Unit-Testing in CakePHP

Warning: Some information my be deprecated! This is still from 1.3 and SimpleTest. CakePHP now uses PHPUnit in 2.x – please see the updated article here.

Some tips you might not have read about yet

Since the cookbook is quite detailed as far as basic testing is concerned I will not unroll that part too much. Please read those pages if you never wrote unit tests before.

Use your own wrapper

If you use your own testcase-wrapper you will be able to add some common functionality for all test cases – similar to AppModel, AppController or AppHelper.
I called it "my_cake_test_case.php" and put it in the vendors folder:

class MyCakeTestCase extends CakeTestCase {
    # added some time calc stuff - like calculating how long a test needed
    //

    # added some more custom stuff
    //

    # printing a header element like <h3> at the beginning of each test function
    function _header($title) {
        if (strpos($title, 'test') === 0) {
            $title = substr($title, 4);
            $title = Inflector::humanize(Inflector::underscore($title));
        }
        return '<h3>'.$title.'</h3>';
    }

    # overriding the assert rules in order to customize it (like adding detailed descriptions and before/after output)
    function assertEqual($is, $expected, $title = null, $value = null, $message = '%s', $options = array()) {
        $expectation = 'EQUAL';
        $this->_printTitle($expectation, $title, $options);
        $this->_printResults($is, $expected, $value, $options);
        return parent::assertEqual($is, $expected, $message);
    }

    function _printTitle($expectation, $title = null) {
        if (!$this->_reporter->params['show_passes']) {
            return false;
        }
        echo $this->_title($expectation, $title);
    }

    function _printResults($is, $expected, $pre = null, $status = false) {
        if (!$this->_reporter->params['show_passes']) {
            return false;
        }

        if ($pre !== null) {
            echo 'value:';
            pr ($pre);
        }
        echo 'result is:';
        pr($is);
        if (!$status) {
            echo 'result expected:';
            pr ($expected);
        }
    }
}

And much more

Now we can use it in the test cases

App::import('Vendor', 'MyCakeTestCase');

class ColorLibTest extends MyCakeTestCase {

    function testColDiff() {
        echo $this->_header(__FUNCTION__);
        $was = 'ffffff';
        $expected = 1;
        $is = $this->Color->colDiff($was, '000000');
        $this->assertEqual($is, $expected, null, $was));
    }

}

The header will be "ColDiff" – generated automatically using FUNCTION (magic constant).
The detailed messages are only displayed if "Show Passes" (url: show_passes=1) is activated. Otherwise it behaves as usual.

Skipping tests based on conditions

Sometimes tests depend on the system settings, enabled modules etc. If it doesn’t make sense to test certain methods, put this at the top of the test function:

# example with "magic_quotes_sybase"
function testSomething() {
    if ($this->skipIf(ini_get('magic_quotes_sybase') === '1', '%s magic_quotes_sybase is on')) {
        return;
    }
    ... // this code will not be executed anymore
}

Always include object integrity first

Cake has a lot of automagic. Sometimes this can hurt, though, if unknown to the programmer.
If a model cannot be initialized it will usually fall back to AppModel – silently! Same with other classes.
So make sure, they are what they are supposed to be.

# this model is not an AppModel instance, but the real deal (Album instance of class Album)
function testAlbumInstance() {
    $this->assertTrue(is_a($this->Album, 'Album'));
}

# for a controller
function testAlbumsControllerInstance() {
    $this->assertTrue(is_a($this->Albums, 'AlbumsController'));
}

startCase() or startTest()

Most of the time startCase() is usually enough. This is triggered ONCE at the beginning of all tests. If the class itself does not remember or change anything, this is more than enough.
But as soon as your class (or object) internally keeps information about the last operation, you will get unexpected results. Use startTest() then to ensure, that the object is always a "fresh" one:

function startTest() {
    $this->Color = new ColorLib();
}

Note: setUp() is the same as startTest() but not officially used because it is SimpleTest specific.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.