Thursday, October 01, 2009

Practical testing in php part 8: mocks

This is the eighth part of the php testing series. You may want to check out the previous parts or subscribe to the feed for being updated on new articles.

In the last part of this series, we have listed the various types of Test Doubles along with the ones that phpunit can easily generate: Stubs and Mocks. The latter are utilized in a different kind of testing than the one presented so far: behavior verification.

The behavior verification testing style differs from the state verification one in the subjects of the assertion methods. While state verification specifies explicit assertion methods to be called upon a test result, behavior verification is focused on checking the actions the system under test undertakes. These actions comprehend which methods it calls, and how many times it does so; but also the parameters it passes to these methods and their order.
The standard interaction with collaborators in object-oriented systems consists of method calls. This kind of testing prescribes to place assertions directly in the overridden methods of Test Doubles, or at the end of every test, to verify that the SUT behavior conforms to specifical rules. These Test Doubles, which can run assertions on their methods parameters, are called Mock Objects (or simply Mocks). The contraposition here is with Stubs, which extend the capabilities of a state based testing but do not make any assumption on method calls or parameters.
Note that the assertions on parameters are placed inside the generated methods, while assertions on method calls are executed by phpunit after the test has run. This means that in a pure behavior verification test you won't find any assert*() calls, which perform state verification.

Now we are going to rewrite the unit test of the previous part taking advantage of phpunit mocks generation, but with a mixed approach which contains also explicit  assertions. The test was about verifying that the GeolocationService class made use of a GoogleMaps collaborator to find out the latitude and longitude of an User object, and the key characteristic was insulation of the test from the GoogleMaps real implementation with a Test Double. You can find the Stub example here.
class GeolocationServiceWithMocksTest extends PHPUnit_Framework_TestCase
    public function testProvidesLatitudeAndLongitudeForAnUser()
        $coordinates = array('latitude' => '42N', 'longitude' => '12E');
        $googleMapsMock = $this->getMock('GoogleMaps', array('getLatitudeAndLongitude'));
        $service = new GeolocationService($googleMapsMock);
        $user = new User;
        $user->location = 'Rome';
        $this->assertEquals('42N', $user->latitude);
        $this->assertEquals('12E', $user->longitude);
The test is actually very similar to the Stub one, but there are some differences:
  • the expect() method of the mock returns an expectation object with a fluent interface we can work with. However, this time a matcher is passed which specifies how many times the mocked method should be called. In the Stub example, the matcher used is $this->any(), that does not run any assertions on the number of calls at the end of the test. Other available matchers are $this->never() and $this->exactly($number). The power of the matchers used in xUnit frameworks is they augment the test's code readability, making it similar to plain English.
  • On the expectation object, along with will() and method(), we are also calling with() to specify the parameter we want to check as passed to getLatitudeAndLongitude(). If we wanted to check more parameters as exact values, we would pass an array to with() containing the actual list. However, we can make also weak assertions by using constraints objects, like $this->attributeEqualTo($name, $value) or $this->isInstanceOf($className), or maybe $this->anything() if no assertion has to be made on a particular parameter.
  • There is no formal definition that says Mocks can't return canned results, as this is often mandatory for the code flow and to complete the test successfully. Though, if you TDD the system under test using mocks without predefined results, it's likely that you will produce a class with a different programming style which works with those tests, and uses mocks very effectively.
  • Whenever you write a with() call or a matcher in expects(), be aware you are building a Mock and not a Stub.
You can find the complete, running test case here on pastebin. I tried to include complete examples in this series to show the practical side of testing instead of tips which are great in theory, but fail to apply in a real situation.

After this example of behavior verification, which makes use of the most advanced phpunit features, we are ready to explore the code coverage features in the next part.

You may want to subscribe to the feed to be updated when new articles in this series are available.

No comments: