w3resource

Understanding Test Doubles in PHPUnit

Introduction

The concept of Test Doubles was introduced by Gerard Meszaros at Meszaros in 2007 with the quote.

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!”

The `createMock($type)` and `getMockBuilder($type)` methods 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 type (interface or class name). This test double object can be used in every context where an object of the original type is expected or required.

The `createMock($type)` method immediately returns a test double object for the specified type (interface or class). The creation of this test double is performed using best practice defaults. The `__construct()` and `__clone()` methods of the original class are not executed and the arguments passed to a method of the test double will not be cloned. If these defaults are not what you need then you can use the `getMockBuilder($type)` method to customize the test double generation using a fluent interface.

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.

Limitation: final, private, and static methods

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 except for static methods that will be replaced by a method throwing a `\PHPUnit\Framework\MockObject\BadMethodCallException` exception.

Stubs

The practice of replacing an object with a test double that (optionally) returns configured return values is referred 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 8.2 shows how to stub method calls and set up return values. We first use the `createMock()` method that is provided by the `PHPUnit\Framework\TestCase` class to set up a stub object that looks like an object of `SomeClass` (Example 8.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 afterward. Instead, you chain method calls as shown in the example. This leads to more readable and “fluent” code.

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

Example 8.2 Stubbing a method call to return a fixed value

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

        // Configure the stub.
        $stub->method('doSomething')
             ->willReturn('foo');

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

?>

Limitation: Methods named “method”

The example shown above only works when the original class does not declare a method named “method”.

If the original class does declare a method named “method” then $stub->expects($this->any())->method('doSomething')->willReturn('foo'); has to be used.

“Behind the scenes”, PHPUnit automatically generates a new PHP class that implements the desired behavior when the createMock() method is used.

Example 8.3 shows an example of how to use the Mock Builder’s fluent interface to configure the creation of the test double. The configuration of this test double uses the same best practice defaults used by createMock().

Example 8.3 Using the Mock Builder API can be used to configure the generated test double class

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->getMockBuilder(SomeClass::class)
                     ->disableOriginalConstructor()
                     ->disableOriginalClone()
                     ->disableArgumentCloning()
                     ->disallowMockingUnknownTypes()
                     ->getMock();

        // Configure the stub.
        $stub->method('doSomething')
             ->willReturn('foo');

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

In the examples so far, we have been returning simple values using `willReturn($value)`. This short syntax is the same as `will($this->returnValue($value))`. We can use variations on this longer syntax to achieve more complex stubbing behavior.

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

Example 8.4 Stubbing a method call to return one of the arguments

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testReturnArgumentStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

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

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

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

?>

When testing a fluent interface, it is sometimes useful to have a stubbed method return a reference to the stubbed object. Example 8.5 shows how you can use returnSelf() to achieve this.

Example 8.5 Stubbing a method call to return a reference to the stub object

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testReturnSelf()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

        // Configure the stub.
        $stub->method('doSomething')
             ->will($this->returnSelf());

        // $stub->doSomething() returns $stub
        $this->assertSame($stub, $stub->doSomething());
    }
}
?>

Sometimes a stubbed method should return different values depending on a predefined list of arguments. You can use returnValueMap() to create a map that associates arguments with corresponding return values. See Example 8.6 for an example.

Example 8.6 Stubbing a method call to return the value from a map

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testReturnValueMapStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

        // Create a map of arguments to return values.
        $map = [
            ['a', 'b', 'c', 'd'],
            ['e', 'f', 'g', 'h']
        ];

        // Configure the stub.
        $stub->method('doSomething')
             ->will($this->returnValueMap($map));

        // $stub->doSomething() returns different values depending on
        // the provided arguments.
        $this->assertSame('d', $stub->doSomething('a', 'b', 'c'));
        $this->assertSame('h', $stub->doSomething('e', 'f', 'g'));
    }
}

?>

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 8.7 for an example.

Example 8.7 Stubbing a method call to return a value from a callback

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testReturnCallbackStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

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

        // $stub->doSomething($argument) returns str_rot13($argument)
        $this->assertSame('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 8.8 for an example.

Example 8.8 Stubbing a method call to return a list of values in the specified order

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testOnConsecutiveCallsStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

        // Configure the stub.
        $stub->method('doSomething')
             ->will($this->onConsecutiveCalls(2, 3, 5, 7));

        // $stub->doSomething() returns a different value each time
        $this->assertSame(2, $stub->doSomething());
        $this->assertSame(3, $stub->doSomething());
        $this->assertSame(5, $stub->doSomething());
    }
}
?>

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

Example 8.9 Stubbing a method call to throw an exception

<?php
use PHPUnit\Framework\TestCase;

class StubTest extends TestCase
{
    public function testThrowExceptionStub()
    {
        // Create a stub for the SomeClass class.
        $stub = $this->createMock(SomeClass::class);

        // Configure the stub.
        $stub->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.

Previous: Understanding Test Doubles in PHPUnit: Best Practices and Examples.
Next: A Gentle introduction to Unit Testing in PHP.



Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://w3resource.com/php/PHPUnit/test-doubles.php