Testing our model

"Kick! Punch! It's all in the mind. If you wanna test me, I'm sure you'll find. The things I'll teach ya is sure to beat ya." - Chop Chop Master Onion's Rap, Parappa the Rapper

Right, let's give this a test! A lot of people leave testing to later on. Not me, no! Over to app/tests/cases/models/EmployeesTest.php:

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

use app\models\Employees;

class EmployeesTest extends \lithium\test\Unit {

    public function setUp() {}

    public function tearDown() {}


}
?>

There's not much going on in here either. Li3 has its own unit testing gubbins - it doesn't use PHPUnit or whatever. I suppose you COULD use PHPUnit (or Behat or SimpleTest), but we're going to stick with the default Li3 testing utilities.

I'm not going to do a full treatment of unit testing or TDD (Test Driven Development) here. The absolute best book on the subject is Kent Beck's Test-Driven Development by example - it really is fantastic!

What we're going to test here is that our model has the fields we'd expect it to. The advantage of defining the schema up front is that we can do this test without needing a database yet! This is cool because if we change our schema and get it wrong, we will know right away that our change has broken something!

Here's my code for app/tests/cases/models/EmployeesTest.php:

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

use app\models\Employees;

class MockEmployees extends Employees {
    protected $_meta = array('connection' => false);
}

class EmployeesTest extends \lithium\test\Unit {
    public function testEmployeeHaveNames() {
        $this->assertEqual('string', MockEmployees::hasField('name')['type']);
    }
}
?>

I've removed setUp and tearDown because we don't need them yet, but we have a whole other class now (MockEmployees) - sorry about that! I'll explain, I promise, but first head over to http://employee-rolodex.localhost/test/ and you should see something like this:

Homepage of the test runner

Here is Li3's web based test runner. OK now you see EmployeesTest under app? Click it! Click it now! Drink your weak lemon drink and click that fussy little link!

What you should get is:

Result of the test runner

1/1 passes - great! Now, I know I promised I'd explain why there's so much code.

You see, Li3 requires a connection to a database (sorta, ish - we'll talk more about connections later :-)). So, if we tried to run this test without MockEmployees in the middle, it tries to connect to the database and, unless you've been a clever sausage and done some reading around, you probably haven't done yet! So, when you come to run the tests, you get all kind of errors. Try clicking a few of the links in the in the test runner under "lithium" - these run the core Li3 tests. Some might not work if you don't have a connection set up.

Now, generally, there are a couple of things you can do with connections and testing:

  1. Disable them
  2. Mock them out
  3. Connect to something

We've gone for option 1 at the moment, but later on we will be mocking connections, and maybe we'll even go so far as connecting to an in-memory database or something. Exciting! For now, though, let's look at these codes:

<?php

// ...
class MockEmployees extends Employees {
    protected $_meta = array('connection' => false);
}
// ...

So we've used inheritance to override connection and set it to false, then we've done all our testing based on that. This prevents Li3's Model class from scurrying around trying to find a database connection. This is a bit icky to do it this way but it does work. Generally, I prefer to test real things as they are where possible - so like I say we'll look at better testing strategies later.

OK so the actual test is quite simple really. Employees::hasField simply returns the field if it has it (I know, I expected it to be a boolean as well! It returns false if the field doesn't exist on the model).

Well, that's the very bare basics of writing a test for a model. Like all stories, I've skipped over some details so we can do some "big reveals" later. You might already have guessed some of the plot twists! Nevertheless, let's hope we can stay engaged with the characters annnnnnd the metaphor just totally fell apart, didn't it?

Let's move on to looking at the controller!