Testing with partial mock objects

Published on and tagged with cakephp  simpletest  testing

When testing your models with unit tests you probably encounter the “problem” of slow test execution due to all those database accesses. And that’s bad. You want the test execution to be fast so you can run the tests often. What can you do to avoid the database accesses?

Well, the SimpleTest framework — the testing framework used by CakePHP — provides a feature called “partial mocks”. With this feature it is possible to fake certain methods of a class, i.e. we can mock those methods which access the database.

Let’s have a look at an example.

We assume we want to test a method which checks whether the provided tags are in the database, and returns then those tags which are not yet in the database.

As we use findAll() to retrieve the tags, we have to mock this method. We can do it with the following snippet:

Mock::generatePartial('Tag', 'MockTag', array('findAll'));

The first parameter of Mock::generatePartial() specifies the class we want to mock, the second parameter defines the name of the mocked class, and the last parameter defines the methods we want to mock.

In the test we will now use the MockTag class instead of the Tag class. It is identical to the Tag class (technically it is a subclass of it), with the difference that the implementation of findAll() doesn’t “exist” anymore. With setReturnValue() we can now specify what findAll() should return when called. Below you find the test code, it should be self-explanatory (if not, please leave a comment):

Mock::generatePartial('Tag', 'MockTag', array('findAll'));

class TagTest extends CakeTestCase {
    function testExtractNewTags() {
        $existingTags = array(0 => array('Tag' => array('name' => 'tagA')), 
			                   1 => array('Tag' => array('name' => 'tagB')));
			
        $mock = new MockTag($this);
        $mock->setReturnValue('findAll', $existingTags);
			
        $newTags = $mock->extractNewTags(array('tagA', 'tagB')); 
        $this->assertIdentical(array(), $newTags);
			
        $newTags = $mock->extractNewTags(array('tagA', 'tagC', 'tagD')); 
        $this->assertEqual(2, count($newTags));
        $this->assertEqual('tagC', $newTags[0]);
        $this->assertEqual('tagD', $newTags[1]);
    }
}

That’s only one simple example of what you can do with partial mock objects. For more see the SimpleTest documentation.

Happy testing :)

Update 2007-12-08: Something I forgot to mention is that you have to include the file mock_objects.php to make it work. You can do it with

vendor('simpletest'.DS.'mock_objects');

at the top of your test case file.

Thanks to Zach Cox for asking me this by email!

6 comments baked

  • Zach Cox

    Thanks for this! It bugs me that “standard” CakePHP unit testing practice is to set up test fixtures in an actual database. Unit testing should not involve databases!

  • cakebaker

    @Zach: Thanks for your comment!

    I think it depends on the situation whether it makes sense to use a database in unit tests. Sometimes it is useful to actually perform the database operations, but in most cases it simply slows down the execution of the tests.

  • Zach Cox

    I think a best practice would be, by default, to not use a database in unit tests, and strive to mock everything outside the class & method you’re testing so it’s completely isolated. A unit test should be testing a “unit” of code, which is one method. Using a database in a unit test should probably be an exception, not a rule.

    Plus, SimpleTest makes it sooo easy to create mock objects, we have no excuse not to! :)

  • cakebaker

    @Zach: Yes, you are right. In a recently started project I use mocks from the beginning and try to avoid using a database for unit testing. Up to now this works fine. The only “disadvantage” I encountered are the array structures you have to fake. They are quite complex, and it is easy to make mistakes… Maybe I will find a solution to make the array handling a bit easier.

  • cakebaker » An alternative way to create arrays

    […] I use more and more mock objects when testing I often have to fake the results of some findAll calls. That means I have to create […]

  • kristofer buffington » Blog Archive » unit testing with cakephp - models

    […] I found Testing with partial mock objects to be a fabulous […]

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License