Prev Next

Chapter 11. Test Doubles

Gerard Meszaros introduces the concept of Test Doubles in [Meszaros2007] like this:

 

Sometimes it is just plain hard to test the system under test (SUT) because it depends on other components that cannot be used in the test environment. This could be because they aren't available, they will not return the results needed for the test or because executing them would have undesirable side effects. In other cases, our test strategy requires us to have more control or visibility of the internal behavior of the SUT.

When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC), we can replace it with a Test Double. The Test Double doesn't have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one!

 
  --Gerard Meszaros

You can use a stub to "replace a real component on which the SUT depends so that the test has a control point for the indirect inputs of the SUT. This allows the test to force the SUT down paths it might not otherwise execute".

Stubs

Example 11.1 shows how to stub method calls and set up return values. We first use the getMock() method that is provided by the PHPUnit_Framework_TestCase class (see Table 22.6) to set up a stub object that looks like an object of SomeClass. We then use the Fluent Interface that PHPUnit provides to specify the behavior for the stub. In essence, this means that you do not need to create several temporary objects and wire them together afterwards. Instead, you chain method calls as shown in the example. This leads to more readable and "fluent" code.

Example 11.1: Stubbing a method call to return a fixed value

<?php
require_once 'PHPUnit/Framework.php';

class SomeClass
{
public function doSomething()
{
// Do something.
}
}

class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMock('SomeClass');

// Configure the stub.
$stub->expects($this->any())
->method('doSomething')
->will($this->returnValue('foo'));

// Calling $stub->doSomething() will now return
// 'foo'.
$this->assertEquals('foo', $stub->doSomething());
}
}
?>

Sometimes you want to return one of the arguments of a method call (unchanged) as the result of a stubbed method call. Example 11.2 shows how you can achieve this using returnArgument() instead of returnValue().

Example 11.2: Stubbing a method call to return one of the arguments

<?php
class SomeClass
{
public function doSomething($argument)
{
// Do something.
}
}

class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnArgumentStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMock('SomeClass');

// Configure the stub.
$stub->expects($this->any())
->method('doSomething')
->will($this->returnArgument(0));

// $stub->doSomething('foo') returns 'foo'
$this->assertEquals('foo', $stub->doSomething('foo'));

// $stub->doSomething('bar') returns 'bar'
$this->assertEquals('bar', $stub->doSomething('bar'));
}
}
?>

When the stubbed method call should return a calculated value instead of a fixed one (see returnValue()) or an (unchanged) argument (see returnArgument()), you can use returnCallback() to have the stubbed method return the result of a callback function or method. See Example 11.3 for an example.

Example 11.3: Stubbing a method call to return a value from a callback

<?php
class SomeClass
{
public function doSomething($argument)
{
// Do something.
}
}

class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnCallbackStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMock('SomeClass');

// Configure the stub.
$stub->expects($this->any())
->method('doSomething')
->will($this->returnCallback('str_rot13'));

// $stub->doSomething($argument) returns str_rot13($argument)
$this->assertEquals('fbzrguvat', $stub->doSomething('something'));
}
}
?>

Instead of returning a value, a stubbed method can also raise an exception. Example 11.4 shows how to use throwException() to do this.

Example 11.4: Stubbing a method call to throw an exception

<?php
class SomeClass
{
public function doSomething()
{
// Do something.
}
}

class StubTest extends PHPUnit_Framework_TestCase
{
public function testThrowExceptionStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMock('SomeClass');

// Configure the stub.
$stub->expects($this->any())
->method('doSomething')
->will($this->throwException(new Exception));

// $stub->doSomething() throws Exception
$stub->doSomething();
}
}
?>

Alternatively, you can write the stub yourself and improve your design along the way. Widely used resources are accessed through a single façade, so you can easily replace the resource with the stub. For example, instead of having direct database calls scattered throughout the code, you have a single Database object, an implementor of the IDatabase interface. Then, you can create a stub implementation of IDatabase and use it for your tests. You can even create an option for running the tests with the stub database or the real database, so you can use your tests for both local testing during development and integration testing with the real database.

Functionality that needs to be stubbed out tends to cluster in the same object, improving cohesion. By presenting the functionality with a single, coherent interface you reduce the coupling with the rest of the system.

Mock Objects

You can use a mock object "as an observation point that is used to verify the indirect outputs of the SUT as it is exercised. Typically, the mock object also includes the functionality of a test stub in that it must return values to the SUT if it hasn't already failed the tests but the emphasis is on the verification of the indirect outputs. Therefore, a mock object is lot more than just a test stub plus assertions; it is used a fundamentally different way".

Sometimes you need to check that an object has been called correctly. Here is an example: suppose we want to test that the correct method, update() in our example, is called on an object that observes another object.

In Example 11.5 we first use the getMock() method that is provided by the PHPUnit_Framework_TestCase class (see Table 22.6) to set up a mock object for the Observer. Since we give an array as the second (optional) parameter for the getMock() method, only the update() method of the Observer class is replaced by a mock implementation.

Example 11.5: Testing that a methods gets called once and with a specified parameter

<?php
require_once 'PHPUnit/Framework.php';

class Subject
{
protected $observers = array();

public function attach(Observer $observer)
{
$this->observers[] = $observer;
}

public function doSomething()
{
// Do something.
// ...

// Notify observers that we did something.
$this->notify('something');
}

protected function notify($argument)
{
foreach ($this->observers as $observer) {
$observer->update($argument);
}
}
}

class Observer
{
public function update($argument)
{
// Do something.
}
}

class ObserverTest extends PHPUnit_Framework_TestCase
{
public function testUpdateIsCalledOnce()
{
// Create a mock for the Observer class.
$observer = $this->getMock('Observer');

// Set up the expectation for the update() method
// to be called only once and with the string 'something'
// as its parameter.
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));

// Create a Subject object and attach the mocked
// Observer object to it.
$subject = new Subject;
$subject->attach($observer);

// Call the doSomething() method on the $subject object
// which we expect to call the mocked Observer object's
// update() method with the string 'something'.
$subject->doSomething();
}
}
?>

Table 22.1 shows the constraints and Table 11.1 shows the matchers that are available.

Table 11.1. Matchers

Matcher Meaning
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any() Returns a matcher that matches when the method it is evaluated for is executed zero or more times.
PHPUnit_Framework_MockObject_Matcher_InvokedCount never() Returns a matcher that matches when the method it is evaluated for is never executed.
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce() Returns a matcher that matches when the method it is evaluated for is executed at least once.
PHPUnit_Framework_MockObject_Matcher_InvokedCount once() Returns a matcher that matches when the method it is evaluated for is executed exactly once.
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count) Returns a matcher that matches when the method it is evaluated for is executed exactly $count times.
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index) Returns a matcher that matches when the method it is evaluated for is invoked at the given $index.

Mocking the Filesystem

vfsStream is a stream wrapper for a virtual filesystem that may be helpful in unit tests to mock the real filesystem.

To install vfsStream, the PEAR channel (pear.php-tools.net) that is used for its distribution needs to be registered with the local PEAR environment:

pear channel-discover pear.php-tools.net

This has to be done only once. Now the PEAR Installer can be used to install vfsStream:

pear install pat/vfsStream-alpha

Example 11.6 shows a class that interacts with the filesystem.

Example 11.6: A class that interacts with the filesystem

<?php
class Example
{
protected $id;
protected $directory;

public function __construct($id)
{
$this->id = $id;
}

public function setDirectory($directory)
{
$this->directory = $directory . DIRECTORY_SEPARATOR . $this->id;

if (!file_exists($this->directory)) {
mkdir($this->directory, 0700, TRUE);
}
}
}?>

Without a virtual filesystem such as vfsStream we cannot test the setDirectory() method in isolation from external influence (see Example 11.7).

Example 11.7: Testing a class that interacts with the filesystem

<?php
require_once 'Example.php';

class ExampleTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (file_exists(dirname(__FILE__) . '/id')) {
rmdir(dirname(__FILE__) . '/id');
}
}

public function testDirectoryIsCreated()
{
$example = new Example('id');
$this->assertFalse(file_exists(dirname(__FILE__) . '/id'));

$example->setDirectory(dirname(__FILE__));
$this->assertTrue(file_exists(dirname(__FILE__) . '/id'));
}

protected function tearDown()
{
if (file_exists(dirname(__FILE__) . '/id')) {
rmdir(dirname(__FILE__) . '/id');
}
}
}
?>

The approach above has several drawbacks:

  • As with any external resource, there might be intermittent problems with the filesystem. This makes tests interacting with it flaky.

  • In the setUp() and tearDown() methods we have to ensure that the directory does not exist before and after the test.

  • When the test execution terminates before the tearDown() method is invoked the directory will stay in the filesystem.

Example 11.8 shows how vfsStream can be used to mock the filesystem in a test for a class that interacts with the filesystem.

Example 11.8: Mocking the filesystem in a test for a class that interacts with the filesystem

<?php
require_once 'vfsStream/vfsStream.php';
require_once 'Example.php';

class ExampleTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
vfsStreamWrapper::register();
vfsStreamWrapper::setRoot(new vfsStreamDirectory('exampleDir'));
}

public function testDirectoryIsCreated()
{
$example = new Example('id');
$this->assertFalse(vfsStreamWrapper::getRoot()->hasChild('id'));

$example->setDirectory(vfsStream::url('exampleDir'));
$this->assertTrue(vfsStreamWrapper::getRoot()->hasChild('id'));
}
}
?>

This has several advantages:

  • The test itself is more concise.

  • vfsStream gives the test developer full control over what the filesystem environment looks like to the tested code.

  • Since the filesystem operations do not operate on the real filesystem anymore, cleanup operations in a tearDown() method are no longer required.

Prev Next
1. Automating Tests
2. PHPUnit's Goals
3. Installing PHPUnit
4. Writing Tests for PHPUnit
Data Providers
Testing Exceptions
Testing PHP Errors
5. The Command-Line Test Runner
6. Fixtures
More setUp() than tearDown()
Variations
Sharing Fixture
Global State
7. Organizing Tests
Composing a Test Suite Using the Filesystem
Composing a Test Suite Using XML Configuration
Using the TestSuite Class
8. TestCase Extensions
Testing Output
Testing Performance
9. Database Testing
Data Sets
Flat XML Data Set
XML Data Set
CSV Data Set
Replacement Data Set
Operations
Database Testing Best Practices
10. Incomplete and Skipped Tests
Incomplete Tests
Skipping Tests
11. Test Doubles
Stubs
Mock Objects
Mocking the Filesystem
12. Testing Practices
During Development
During Debugging
13. Test-Driven Development
BankAccount Example
14. Behaviour-Driven Development
BowlingGame Example
15. Code Coverage Analysis
Specifying Covered Methods
Ignoring Code Blocks
Including and Excluding Files
16. Other Uses for Tests
Agile Documentation
Cross-Team Tests
17. Skeleton Generator
Generating a Test Case Class Skeleton
Generating a Class Skeleton from a Test Case Class
18. PHPUnit and Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
19. Logging
Test Results (XML)
Test Results (TAP)
Test Results (JSON)
Code Coverage (XML)
Test Database
20. Build Automation
Apache Ant
Apache Maven
Phing
21. Continuous Integration
Atlassian Bamboo
CruiseControl
phpUnderControl
22. PHPUnit API
Overview
PHPUnit_Framework_Assert
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertLessThan()
assertLessThanOrEqual()
assertNotNull()
assertObjectHasAttribute()
assertRegExp()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEqualsFile()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
PHPUnit_Framework_Test
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestSuite
PHPUnit_Framework_TestResult
Package Structure
23. Extending PHPUnit
Subclass PHPUnit_Framework_TestCase
Assert Classes
Subclass PHPUnit_Extensions_TestDecorator
Implement PHPUnit_Framework_Test
Subclass PHPUnit_Framework_TestResult
Implement PHPUnit_Framework_TestListener
New Test Runner
A. Assertions
B. The XML Configuration File
PHPUnit
Test Suite
Groups
Including and Excluding Files for Code Coverage
Logging
Setting PHP INI settings, Constants and Global Variables
Configuring Browsers for Selenium RC
C. Index
D. Bibliography
E. Copyright