7. Test - Models

7.1 Overview

Model tests are stored in /test/unit. Once we have some fixtures written, testing the model functionality is quite straight forward. We specify what data to load, and make sure that our model functionality returns the correct data when it is executed.

Each unit test class is named after the model class with a "Test" suffix and extends Mad_Test_Unit. Most of these details are already handled when the stubs are generated for the model file. Tests classes have two special methods that are optional. setUp() will execute before each test, and tearDown() will execute after each test finishes.

class FolderTest extends Mad_Test_Unit
{
    public function setUp()
    {
        // this code is run before each test
    }
    
    public function tearDown()
    {
        // this code is run after each test
    }
}

Any method within the class that begins with the prefix 'test' will be considered a test and will be executed when PHPUnit runs. Most of the time you will write at least one test per public method in your model. More often than not you will write more.

class FolderTest extends Mad_Test_Unit
{
    // this is considered a test
    public function testGetParentFolders()
    {
    }

    // this is not a test (not prefixed with 'test')
    public function doSomething()
    {
    }
}

Most tests will be performed with the use of PHPUnit's assertion methods. When the test executes, PHPUnit ensures that each assertion performs correctly in order for the test to pass. The three most common assertions are assertTrue, assertFalse, and assertEquals.

class FolderTest extends Mad_Test_Unit
{
    // this is considered a test
    public function testGetParentFolders()
    {
        ...
        // assert that the object is a Folder
        $this->assertTrue($folder instanceof Folder);
          
        // make sure the name isn't empty
        $this->assertFalse(empty($folder->name));

        // assert that the description equals the given string
        $this->assertEquals('The Description', $folder->description)
        ...
    }
}

7.2 Loading Fixtures

Loading fixture data in a unit test is quite simple. It can be put in the setUp() method which gets run before each test, or can be loaded individually per test.

// load the fixture for all tests
public function setUp()
{
    // load a single fixture file
    $this->fixtures('folders');

    // load multiple fixture files
    $this->fixtures('documents', 'document_types');
}

// load the fixture for one test
public function testSomeFunctionality()
{
    $this->fixtures('folders');
}

7.3 Data By Name

When a fixture gets loaded, we also get access to the records in the fixture file by name. This allows us to write tests that are more resilient to changes within the fixture. Each name has an associative array of values from the fixture

# fixture data in folders.yml
public:
  id:        123
  parent_id: 0
  name:      1. Documents
  path:      ./1. Documents

...

// access the fixture record by name
public function testSomeFunctionality()
{
    $this->fixtures('folders');

    // a Folder object is instantiated with the fixture data    
    $folder = $this->folders('public');

    // assert the name is correct
    $this->assertEquals('1. Documents', $folder->name);
}

7.4 Database Access

Most of the tests written to test Model functionality will not need to access the database directly, but will instead verify data from a fixture file. If you need to access the database directly you can do so by using $this->_conn.

// test to query data
public function testQuerySomeData()
{
    $results = $this->_conn->select("SELECT * FROM folders");
    foreach ($results as $row) {
        print $row['name'];
    }
}

7.5 Test Helpers

If we have custom assertion or test helper methods, we can share them using the MadTestHelper class. Adding public methods to test/MadTestHelper.php will make them accessible from all of our unit and functional tests.

class MadTestHelper
{
    public function clearUploads()
    {
        Mad_Support_FileUtils::rm_rf(UPLOAD_DIR);
    }
}
... 
class DocumentTest extends Mad_Test_Unit
{
  public function setUp()
  {
      $this->clearUploads();
  }
}

7.6 Unit Assertions

There are a few bonus assertions that Mad_Test_Unit adds that are pretty useful when testing model data.

7.6.1 assertDifference

This assertion uses a block style syntax to make sure that a expression given yields different results after a block of code has executed. The first argument to this assertion is an expression. The second is an integer that represents the difference between the expression's result before and after the block is finished. The block is anything that comes before we call the end() method.

make sure that the count is +1 after create() finishes
$diff = $this->assertDifference('User::count()', 1);
    User::create(array('username' => 'lebowski'));
$diff->end();

The difference expected in this case is that User::count() will increase by 1 after the creation is finished. This is an optional argument, and will default to 1 when not specified. We can alternately use a negative number if the net count would have decreased during the block.

make sure that the count is -1 after delete() finishes
$diff = $this->assertDifference('User::count()', -1);
    User::delete(1);
$diff->end();

7.6.2 assertNoDifference

This assertion works very similarly to its counterpart, but simply asserts that no change takes place when the expression is evaluated before and after the block executes.

make sure that the count is the same after create() finishes
$diff = $this->assertNoDifference('User::count()');
    User::create(array('username' => ''));
$diff->end();