Super pro testing with fixtures

We made some progress with testing, but really, we need to be able to write to and read from some kind of database to simulate real-world usage. In the last chapter, we set up our environment to have a connection just for testing. Now, we're going to use fixtures. Fixtures are a way of setting the system up with some mock data, where we know exactly what said data is and it's the same every time, so we can make our assertions against that.

Fixtures is a library that Union Of RAD supply, available at https://github.com/UnionOfRAD/li3_fixtures. Here's how I installed it:

cd app/libraries
git clone https://github.com/UnionOfRAD/li3_fixtures.git

Then include the library in app/config/bootstrap/libraries.php

Libraries::add('li3_fixtures');

A fixture is basically a set of known-state data. So, rather than testing with a system with any old data in, we know exactly what to expect.

We have a few things to do. In app/tests/fixture/EmployeesFixture.php, put the following code:

<?php
namespace app\tests\fixture;

class EmployeesFixture extends \li3_fixtures\test\Fixture {

    protected $_model = 'app\models\Employees';

    protected $_fields = array(
        'id' => array('type' => 'id'),
        'name' => array('type' => 'string'),
        'notes' => array('type' => 'string'),
        'department' => array('type' => 'string'),
    );

    protected $_records = array(
        array('id' => 1, 'name' => 'Foobar', 'notes' => 'some stuff2'),
        array('id' => 2, 'name' => 'Bazbip', 'notes' => 'some other stuff')
    );
}
?>

What we've just defined here is a fixture for the Employees model that has some canned data in the $_records field. We've got two users that we've created there called "Foobar" and "Bazbip". Next thing we're going to do is to run the controller's index method using this test data. Back over to app/tests/cases/controllers/EmployeesControllerTest.php:

<?php
namespace app\tests\cases\controllers;

use app\controllers\EmployeesController;
use lithium\action\Request;
use li3_fixtures\test\Fixtures;

class EmployeesControllerTest extends \lithium\test\Unit {
    public function setUp() {
        Fixtures::config(array(
            'db' => array(
                'adapter' => 'Connection',
                'connection' => 'default',
                'fixtures' => array(
                    'Employees' => 'app\tests\fixture\EmployeesFixture',
                )
            )
        ));
        Fixtures::save('db');
    }

    public function tearDown() {
        Fixtures::clear('db');
    }

    public function testIndexListsEmployeesInTheDatabase() {
        $request = new Request();
        $request->data = array();
        $controller = new EmployeesController(array('request' => $request));

        $result = $controller->index();
        $this->assertEqual("Foobar", $result['employees'][1]->name);
        $this->assertEqual("Bazbip", $result['employees'][2]->name);
        $this->assertNull($result['employees'][3]);
    }
// public function testView() {}
// public function testAdd() {}
// public function testEdit() {}
// public function testDelete() {}
}
?>

So I've chucked out the testIndex method and written testIndexListsEmployeesInTheDatabase, which is pretty similar but we're actually verifying the results that come from the SQLite3 database come through correctly. The setUp method is saying "hey let's use the test database and let's load the Employees Fixture into it". The tearDown method clears out the database that's connected to in setUp.

Exercise: using this test connection from within your unit test, run Employees::all(); and print out their names. You should see that they're coming from the test connection. This is cool because we've swapped the connection out for testing so we don't have to change our code.

So now, we've got a pretty cool test that has a temporary database, puts some data in there, then runs the controller using that temporary database and checks the output. Nice!

$ ./libraries/lithium/console/li3 test \
    app/tests/cases/controllers/EmployeesControllerTest.php
----
Test
----

...

OK

3 / 3 passes
0 fails and 0 exceptions

Notes on running tests in Li3

  • Each "test*" method has setUp automatically run before it and tearDown run at the end.
  • If you're used to PHPUnit, one quirk of Li3's testing is that, if you're expecting it to stop if an assertion fails! It'll carry on executing the test!

If you want more detail on fixtures (and I'd say it's a good thing to get fixated on), check out li3 fixtures on Github and read through the documentation.