| Prev | Next |
Gerard Meszaros introduces the concept of Test Doubles in [Meszaros2007] like this:
The getMock($className) method provided by PHPUnit can be used in a test to automatically generate an object that can act as a test double for the specified original class. This test double object can be used in every context where an object of the original class is expected.
By default, all methods of the original class are replaced with a dummy implementation that just returns NULL (without calling the original method). Using the will($this->returnValue()) method, for instance, you can configure these dummy implementations to return a value when called.
Please note that final, private and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior.
The practice of replacing an object with a test double that (optionally) returns configured return values is refered to as stubbing. 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".
Example 11.2 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 to set up a stub object that looks like an object of SomeClass (Example 11.1). 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: The class we want to stub
<?php
class SomeClass
{
public function doSomething()
{
// Do something.
}
}
?>
Example 11.2: Stubbing a method call to return a fixed value
<?php
require_once 'SomeClass.php';
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());
}
}
?>
"Behind the scenes", PHPUnit automatically generates a new PHP class that implements the desired behavior when the getMock() method is used. The generated test double class can be configured through the optional arguments of the getMock() method.
By default, all methods of the given class are replaced with a test double that just returns NULL unless a return value is configured using will($this->returnValue()), for instance.
When the second (optional) parameter is provided, only the methods whose names are in the array are replaced with a configurable test double. The behavior of the other methods is not changed.
The third (optional) parameter may hold a parameter array that is passed to the original class' constructor (which is not replaced with a dummy implementation by default).
The fourth (optional) parameter can be used to specify a class name for the generated test double class.
The fifth (optional) parameter can be used to disable the call to the original class' constructor.
The sixth (optional) parameter can be used to disable the call to the original class' clone constructor.
The seventh (optional) parameter can be used to disable __autoload() during the generation of the test double class.
Alternatively, the Mock Builder API can be used to configure the generated test double class. Example 11.3 shows an example. Here's a list of the methods that can be used with the Mock Builder's fluent interface:
setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed.
setConstructorArgs(array $args) can be called to provide a parameter array that is passed to the original class' constructor (which is not replaced with a dummy implementation by default).
setMockClassName($name) can be used to specify a class name for the generated test double class.
disableOriginalConstructor() can be used to disable the call to the original class' constructor.
disableOriginalClone() can be used to disable the call to the original class' clone constructor.
disableAutoload() can be used to disable __autoload() during the generation of the test double class.
Example 11.3: Using the Mock Builder API can be used to configure the generated test double class
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMockBuilder('SomeClass')
->disableOriginalConstructor()
->getMock();
// 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.4 shows how you can achieve this using returnArgument() instead of returnValue().
Example 11.4: Stubbing a method call to return one of the arguments
<?php
require_once 'SomeClass.php';
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.5 for an example.
Example 11.5: Stubbing a method call to return a value from a callback
<?php
require_once 'SomeClass.php';
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'));
}
}
?>
A simpler alternative to setting up a callback method may be to specify a list of desired return values. You can do this with the onConsecutiveCalls() method. See Example 11.6 for an example.
Example 11.6: Stubbing a method call to return a list of values in the specified order
<?php
require_once 'SomeClass.php';
class StubTest extends PHPUnit_Framework_TestCase
{
public function testOnConsecutiveCallsStub()
{
// Create a stub for the SomeClass class.
$stub = $this->getMock('SomeClass');
// Configure the stub.
$stub->expects($this->any())
->method('doSomething')
->will($this->onConsecutiveCalls(2, 3, 5, 7));
// $stub->doSomething() returns a different value each time
$this->assertEquals(2, $stub->doSomething());
$this->assertEquals(3, $stub->doSomething());
$this->assertEquals(5, $stub->doSomething());
}
}
?>
Instead of returning a value, a stubbed method can also raise an exception. Example 11.7 shows how to use throwException() to do this.
Example 11.7: Stubbing a method call to throw an exception
<?php
require_once 'SomeClass.php';
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.
The practice of replacing an object with a test double that verifies expectations, for instance asserting that a method has been called, is refered to as mocking.
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".
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. Example 11.8 shows the code for the Subject and Observer classes that are part of the System under Test (SUT).
Example 11.8: The Subject and Observer classes that are part of the System under Test (SUT)
<?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');
}
public function doSomethingBad()
{
foreach ($this->observers as $observer) {
$observer->reportError(42, 'Something bad happened', $this);
}
}
protected function notify($argument)
{
foreach ($this->observers as $observer) {
$observer->update($argument);
}
}
// Other methods.
}
class Observer
{
public function update($argument)
{
// Do something.
}
public function reportError($errorCode, $errorMessage, Subject $subject)
{
// Do something
}
// Other methods.
}
?>
Example 11.9 shows how to use a mock object to test the interaction between Subject and Observer objects.
We first use the getMock() method that is provided by the PHPUnit_Framework_TestCase class 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.9: Testing that a method gets called once and with a specified argument
<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
public function testObserversAreUpdated()
{
// Create a mock for the Observer class,
// only mock the update() method.
$observer = $this->getMock('Observer', array('update'));
// 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();
}
}
?>
The with() method can take any number of arguments, corresponding to the number of parameters to the method being mocked. You can specify more advanced constraints on the method argument than a simple match.
Example 11.10: Testing that a method gets called with a number of arguments constrained in different ways
<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
public function testErrorReported()
{
// Create a mock for the Observer class, mocking the
// reportError() method
$observer = $this->getMock('Observer', array('reportError'));
$observer->expects($this->once())
->method('reportError')
->with($this->greaterThan(0),
$this->stringContains('Something'),
$this->anything());
$subject = new Subject;
$subject->attach($observer);
// The doSomethingBad() method should report an error to the observer
// via the reportError() method
$subject->doSomethingBad();
}
}
?>
Table 4.2 shows the constraints that can be applied to method arguments and Table 11.1 shows the matchers that are available to specify the number of invocations.
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.
|
The getMockForAbstractClass() method returns a mock object for an abstract class. All abstract methods of the given abstract class are mocked. This allows for testing the concrete methods of an abstract class.
Example 11.11: Testing the concrete methods of an abstract class
<?php
abstract class AbstractClass
{
public function concreteMethod()
{
return $this->abstractMethod();
}
public abstract function abstractMethod();
}
class AbstractClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$stub = $this->getMockForAbstractClass('AbstractClass');
$stub->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));
$this->assertTrue($stub->concreteMethod());
}
}
?>
When your application interacts with a web service you want to test it without actually interacting with the web service. To make the stubbing and mocking of web services easy, the getMockFromWsdl() can be used just like getMock() (see above). The only difference is that getMockFromWsdl() returns a stub or mock based on a web service description in WSDL and getMock() returns a stub or mock based on a PHP class or interface.
Example 11.12 shows how getMockFromWsdl() can be used to stub, for example, the web service described in GoogleSearch.wsdl.
Example 11.12: Stubbing a web service
<?php
class GoogleTest extends PHPUnit_Framework_TestCase
{
public function testSearch()
{
$googleSearch = $this->getMockFromWsdl(
'GoogleSearch.wsdl', 'GoogleSearch'
);
$directoryCategory = new StdClass;
$directoryCategory->fullViewableName = '';
$directoryCategory->specialEncoding = '';
$element = new StdClass;
$element->summary = '';
$element->URL = 'http://www.phpunit.de/';
$element->snippet = '...';
$element->title = '<b>PHPUnit</b>';
$element->cachedSize = '11k';
$element->relatedInformationPresent = TRUE;
$element->hostName = 'www.phpunit.de';
$element->directoryCategory = $directoryCategory;
$element->directoryTitle = '';
$result = new StdClass;
$result->documentFiltering = FALSE;
$result->searchComments = '';
$result->estimatedTotalResultsCount = 378000;
$result->estimateIsExact = FALSE;
$result->resultElements = array($element);
$result->searchQuery = 'PHPUnit';
$result->startIndex = 1;
$result->endIndex = 1;
$result->searchTips = '';
$result->directoryCategories = array();
$result->searchTime = 0.248822;
$googleSearch->expects($this->any())
->method('doGoogleSearch')
->will($this->returnValue($result));
/**
* $googleSearch->doGoogleSearch() will now return a stubbed result and
* the web service's doGoogleSearch() method will not be invoked.
*/
$this->assertEquals(
$result,
$googleSearch->doGoogleSearch(
'00000000000000000000000000000000',
'PHPUnit',
0,
1,
FALSE,
'',
FALSE,
'',
'',
''
)
);
}
}
?>
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.13 shows a class that interacts with the filesystem.
Example 11.13: 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.14).
Example 11.14: 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.15 shows how vfsStream can be used to mock the filesystem in a test for a class that interacts with the filesystem.
Example 11.15: 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 |
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
Copyright © 2005-2012 Sebastian Bergmann.