Loading Fixtures Inside Functional Tests

Since I last wrote about Winning Side Ministries’ website (the one I was writing a custom CMS for) I’ve changed my mind yet again. Yes, I tend to do that a lot. I’m currently rebuilding the website based on the Symfony2 PHP web framework. It’s based on MVC and presents a nice clean interface to which one can code pretty much anything web-based and has lots of great built in tools for getting the job done.

Annoyed by past experiences with code-first-test-last development flow I’ve decided to try Test Driven Development for this particular project and I’m finding that I really like it. I will admit that there have been times when I’ve had to sit and ponder over how to bring about certain changes by first writing tests, but they’ve all been situations that I’ll run into time and time again so it hasn’t been time lost. One of those places was the first time I needed to begin writing the code to drive a view (aka, the page that will be rendered in the browser). Since this involves combining multiple components, the tests preceding development must by definition be functional tests; of course this in turn requires that we write some data fixtures so we’ll have something to run the tests against.

The Symfony framework by default uses an ORM called Doctrine, and happily enough there is a bundle (Symfony term that more or less equates to a module) called DoctrineFixturesBundle that handles most of the grunt work of maintaining the fixtures for you.  On the documentation page for the bundle it outlines the steps required for writing the fixtures and loading them into the database, though it only shows how to load them manually from the command line.  This puzzled me since one would think that it would be wiser to reload the fixtures each time the functional tests are run so that in the tests we can do all the CRUD on the database we want without worrying about spoiling things for the next run of tests.  That’s when I set off to find out how I could clear the database and load fresh fixtures at the beginning of each test run.

To start off, here is the class I came up with:

Let’s break it down a bit since a few of the constituent pieces of code are significant in their own right.

Base Class

We will be extending the class that has been provided for us in Symfony to facilitate functional tests: WebTestCase. It’s just a subclass of PHPUnit_Framework_TestCase that has a built-in web crawler to allow us to make requests and check the responses. The namespace defined there is where I keep my custom testing classes.

In PHPUnit 3.6.x if we want to call code just before all the tests in a *_TestCase subclass are run, we put that code in a public method called setUpBeforeClass() (as opposed to setUp() which is called before every individual test). Its destructive counterpart is tearDownAfterClass() just as you may expect. These methods will contain our code for loading the fixtures and then wiping them out when we’re done. If we need to use setUpBeforeClass() or tearDownAfterClass() in classes that extend FreshFixtureWebTestCase we have to be sure to call the parent:: version as well or our code here won’t be executed.

Accessing the Entity Manager

To get started, we need to grab a reference to EntityManager. This is pretty common code inside a WebTestCase instance so I’ll not go into the explanation, but here it is. Notice that I’ve assigned our reference to the Container to a static class variable…this is just a convenience for our subclasses and is not required.

Retrieving Entity class meta-data

Now comes the fun stuff. We don’t want to have to worry about the state of the database schema or migrations in the tests, so I like to dump the database and totally reload the schema directly from the Entities before the tests run. To do that we get a reference to the MetaDataFactory from our EntityManager and call its getAllMetaData() method. This returns an object that contains the meta-data for all our Entity classes.

Using SchemaTool to modify the database

The question is what do we with the meta-data once we have it? Symfony comes with a nice helper class called the SchemaTool. There are several different things we can do with this class including drop our current database and create a schema from meta-data.

Creating a CLI application object

The doctrine-fixtures bundle only gives us the ability to load fixtures from the command line. In order to run Symfony CLI commands from PHP code we need to create an Application object. We have to make sure we set autoExit to false or else our entire PHP script will exit as soon as the command has run—this would be rather counterproductive when we’re trying to run tests!

Running the doctrine:fixtures:load command

To actually run our command we create input and output objects and feed them to the Application object. The command to run goes into the input and the result (which would normally be printed to the terminal) will be read from the output. Our command will be in the form of a string so we’ll use StringInput. We want the output of the command in a string as well but unfortunately there is no StringOutput class; there is a however a StreamOutput which we can point to a temporary file. Once the command is done executing we can read the contents of the file back. Note that PHP automatically deletes files created with tmpfile() once the script exits so we don’t have to bother with it.

Checking for errors

If there were any errors the output of the command should contain the word ‘Exception’. I’m just running an assert to make sure we don’t see that in the output, though you may have to use a different method if you have a table or Entity with ‘Exception’ in the name.

Cleaning up

Once all our tests are done we want to clear the database again. This way if we accidentally forget to load something in another test class we’ll know immediately because all queries will fail.

Using FreshFixtureWebTestCase

To use this class we simply extend it the same way we would WebTestCase. All the fixture-loading code will be called automatically without so much as a thought from us, though I do want to reiterate my prior warning:

If we define setUpBeforeClass() or tearDownAfterClass() in a subclass of FreshFixtureWebTestCase, FreshFixtureWebTestCases’s method(s) will be overridden. To keep the fixture functionality we must call parent::[method_name] from within whichever method(s) we override.

Advertisements