PHP UK 2016 logo

Mocking Dependencies in Unit Tests with Phake

Questions or Feedback?

Slides

Linked from either of these:

joind.in/talk/b501b

matthewturland.com/publications

Goals

  • Learn about the general practice of mocking, why it's done, and related terminology
  • Review the high-level implementation of PHP mocking libraries in general
  • Discuss the commonly used features of Phake mocking library and how to use them

Non-Goals

Getting Phake

Install using Composer:


{
    "require-dev": {
        "phake/phake": "^2"
    }
}
                    

Or clone the git repository:
github.com/mlively/Phake

Just Use Composer

Seriously, just use Composer.

Composer is awesomesauce. I put it on everything.

Also, RTFM

phake.readthedocs.org

Mike Lively

Mike Lively

github.com/mlively

Unit Testing

In computer programming, unit testing is a software testing method... The goal of unit testing is to isolate each part of the program and show that the individual parts are correct.
Wikipedia

PHPUnit

PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks.
PHPUnit project web site

Sebastian Bergmann

Sebastian Bergmann

sebastian-bergmann.de

thephp.cc

Integration Testing

Integration testing... is the phase in software testing in which individual software modules are combined and tested as a group. It occurs after unit testing...
Wikipedia

Integration Test Example


public function testGetUsers() {
    $model = new UserModel(new \PDO('...'));
    $users = $model->getUsers();
    // ...
}
            

Why Unit Tests?

  • You may not want tests interacting with external systems (e.g 3rd party APIs)
  • Changes to a component can break integration tests for its dependents

Example Class Under Test


use GuzzleHttp\ClientInterface;

class FooRestClient {
    protected $client;

    public function __construct(ClientInterface $client) {
        $this->client = $client;
        /* ... */
    }

    /* ... */
}
            

How do we test this class in isolation from the ClientInterface instance injected into its constructor?

Test Doubles

In automated unit testing, it may be necessary to use objects... that look and behave like their release-intended counterparts, but are actually simplified versions... A test double is a generic... term used for these objects...
Wikipedia

Mock Object

In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways... to test the behavior of some other object.
Wikipedia

Mocking Classes

For mock objects to pass typehint checks:

  • When mocking a class, the mock object must be an instance of a subclass of that class
  • When mocking an interface, the mock object must be an instance of a class that implements that interface

Mock Objects in Phake

Using the previous class under test example:


class FooRestClientTest extends PHPUnit_Framework_TestCase {
    public function testConstructor() {
        $http = Phake::mock(ClientInterface::class);
        echo get_class($http), PHP_EOL;
        $client = new FooRestClient($http);
    }
}
            

ClientInterface_PHAKE5556f19936c6c
            

Stubs

The practice of replacing an object with a test double that... returns configured return values is referred to as stubbing.
PHPUnit manual

Example Method to Stub


use GuzzleHttp\ClientInterface;

class FooRestClient {
    /* ... */

    public function getThing($id) {
        // The two methods called below need to be stubbed
        $response = $this->client->get(
            'https://api.foo.com/things/' . $id
        );
        return $response->json();
    }
}
            

Example Test With Stubbing


use GuzzleHttp\ClientInterface;
use GuzzleHttp\Message\ResponseInterface;

$data = ['id' => 1, 'name' => 'Thing One'];

$response = Phake::mock(ResponseInterface::class);
Phake::when($response)->json()->thenReturn($data);

$http = Phake::mock(ClientInterface::class);
Phake::when($http)
    ->get('https://api.foo.com/things/1')
    ->thenReturn($response);

$client = new FooRestClient($http);
$thing = $client->getThing('1');
$this->assertSame($thing, $data);
            

Stub Behaviors

Phake refers to these as answers.

By default, mock object methods return null.

  • thenReturn($value)
  • thenReturnCallback($callable)
  • thenThrow($exception)
  • Custom answers

private Methods

Testing private Methods


class MyReallyTerribleOldClass {
    private function cleanRowContent($dbRow) {
        return 42;
    }
}
            

$dbRow = ['foo' => 'bar'];
$oldClass = Phake::partialMock('MyReallyTerribleOldClass');
$data = Phake::makeVisible($oldClass)->cleanRowContent($dbRow);
            

Testing private Methods


class PrivateExample {
    private function privateMethod() {
        return 42;
    }
}

// ...

$method = new \ReflectionMethod('PrivateExample', 'privateMethod');
$method->setAccessible(true);
$this->assertEquals(42, $method->invoke(new PrivateExample));
            

static Methods

static methods can't be stubbed
unless they're invoked using late static binding

Stubbing static Method Calls


class Foo {
    public $logger = 'Logger';
    public function doSomething() {
        $logger = $this->logger;
        $logger::logData($data);
    }
}
            

class FooTest {
    public function testDoSomething() {
        $foo = new Foo;
        $foo->logger = Phake::mock('Logger');
        $foo->doSomething();
        Phake::verify($foo->logger)
            ->logData(Phake::anyParameters());
    }
}
            

Not sure this works right yet...

Stubbing static Method Calls


class ClassWithStaticMethod {
    public static function staticMethod() {
        return 42;
    }
}
class DependentClass {
    /* Wrap the static method call in an instance method ... */
    protected function callStaticMethod() {
        return ClassWithStaticMethod::staticMethod();
    }
}
            

$mock = Phake::mock('DependentClass');
/* ... then stub that instance method */
Phake::when($mock)->callStaticMethod()->thenReturn(37);
            

final Methods

final methods can't be stubbed
because subclasses can't override them


class FinalExample {
    public final function finalMethod() {
        return 42;
    }
}
$mock = Phake::mock('FinalExample');
Phake::when($mock)->finalMethod()->thenReturn(37);
var_dump($mock->finalMethod());
            

int(42)
            

final classes can't be mocked or stubbed at all because they can't be subclassed.

Stubbing final Method Calls


class ClassWithFinalMethod {
    public final function finalMethod() {
        return 42;
    }
}
class DependentClass {
    private $instance;
    public function __construct(ClassWithFinalMethod $instance) {
        $this->instance = $instance;
    }
    /* Wrap the final method call in an instance method ... */
    protected function callStaticMethod() {
        return $this->instance->finalMethod();
    }
}
            

$mock = Phake::mock('ClassWithFinalMethod');
/* ... then stub that instance method */
Phake::when($mock)->callFinalMethod()->thenReturn(37);
            

Magic Methods

All magic methods can be stubbed normally
except for __call()


class ClassWithCall {
    public function __call($method, $args) {
        return 42;
    }
}
$mock = Phake::mock('ClassWithCall');
/* Either stub the call that would trigger __call() ... */
Phake::when($mock)->nonExistentMethod()->thenReturn(37);
/* ... or stub __call() itself */
Phake::whenCallMethodWith('nonExistentMethod', [])
    ->isCalledOn($mock)->thenReturn(37);
            

Stubbing Multiple Calls


class Adder {
    public function add($x, $y) {
        return $x + $y;
    }
}
$mock = Phake::mock('Adder');
Phake::when($mock)->add(0, 2)->thenReturn(2);
Phake::when($mock)->add(2, -2)->thenReturn(0);
Phake::when($mock)->add(0, -2)->thenReturn(-2);
            

Stubbing Consecutive Calls


class FileObject {
    public function readBytes() { /* ... */ }
}
$mock = Phake::mock('FileObject');
Phake::when($mock)->readBytes()
    ->thenReturn('foo')  // first call
    ->thenReturn('bar')  // second call
    ->thenReturn('baz'); // third and subsequent calls
            

Overwriting Stubs


class OverwriteExample {
    public function getValue() { /* ... */ }
}
$mock = Phake::mock('OverwriteExample');
/* Define the stub */
Phake::when($mock)->getValue()->thenReturn(42);
/* Then re-define to overwrite it */
Phake::when($mock)->getValue()->thenReturn(37);
var_dump($mock->getValue());
            

int(37)
            

Method Verification

Mock objects in Phake can almost be viewed as a tape recorder... Phake::verify() will look at that recording and allow you to assert whether or not a certain call was made.

Phake manual

Example Method Call to Verify


class EventEmitter {
    public function emit($event, $args) { /* ... */ }
}
class Plugin {
    public function __construct(EventEmitter $emitter) {
        $this->emitter = $emitter;
    }
    public function doThing() {
        /* This call must be verified */
        $this->emitter->emit('thing.happened', ['stuff!']);
    }
}
            

Example Test to Verify


$emitter = Phake::mock('EventEmitter');
$plugin = new Plugin($emitter);
$plugin->doThing();
Phake::verify($emitter)->emit('thing.happened', ['stuff!']);
            

Verifying Different Invocations


class MultipleCallClass {
    public function calledMethod($arg) { /* ... */ }
}
$mock = Phake::mock('MultipleCallClass');
$mock->calledMethod('foo');
$mock->calledMethod('bar');
Phake::verify($mock)->calledMethod('bar');
Phake::verify($mock)->calledMethod('foo');
            

Verifying Invocation Order


class OrderClass {
    public function doThing() { /* ... */ }
    public function doLastThing() { /* ... */ }
    public function doOtherThing() { /* ... */ }
}
$mock = Phake::mock('OrderClass');
$mock->doThing();
$mock->doOtherThing();
$mock->doLastThing();
Phake::inOrder(
    Phake::verify($mock)->doThing(),
    Phake::verify($mock)->doOtherThing(),
    Phake::verify($mock)->doLastThing()
);
            

Verifying Identical Invocations


$mock->calledMethod('foo');
$mock->calledMethod('foo');
Phake::verify($mock, Phake::times(2))->calledMethod('foo');
            

These also work:

  • Phake::atLeast($n)
  • Phake::atMost($n)
  • Phake::never()

Verifying No Interaction


class ThingDoer {
    public function doThing() { /* ... */
}
class StuffDoer {
    public function __construct(ThingDoer $thing) {
        $this->thing = $thing;
    }
    public function doStuff($doThing) {
        if ($doThing) { $this->thing->doThing(); }
    }
}
$thing = Phake::mock('ThingDoer');
$stuff = new StuffDoer($thing);
$stuff->doStuff(false);
Phake::verifyNoInteraction($thing);
            

Verifying No Further Interaction


$thing = Phake::mock('ThingDoer');
$stuff = new StuffDoer($thing);
$stuff->doStuff(true);
Phake::verifyNoFurtherInteraction($thing);
$stuff->doStuff(false);
            

Verifying Unverified Interaction


$filter = new MyFilter();
$list = Phake::Mock('MyList');
$filter->addEvenToList([1, 2, 3, 4, 5], $list);
Phake::verify($list)->push(2);
Phake::verify($list)->push(4);
Phake::verifyNoOtherInteractions($list);
            

Verifying Magic Methods

Same story, second verse as stubbing magic methods


class ClassWithCall {
    public function __call($method, $args) {
        return 42;
    }
}
$mock = Phake::mock('ClassWithCall');
/* Either stub the call that would trigger __call() ... */
Phake::verify($mock)->nonExistentMethod();
/* ... or verify the call to __call() itself */
Phake::verifyCallMethodWith('nonExistentMethod', [])
    ->isCalledOn($mock);
            

PHPUnit Constraints

Phake supports using PHPUnit constraints to make assertions about stubbed and verified calls.


class Shuffler {
    public function shuffle($times) { /* ... */ }
}
class ShufflerTest extends PHPUnit_Framework_TestCase {
    public function testShuffle() {
        $shuffler = Phake::mock('Shuffler');
        $shuffler->shuffle(11);
        Phake::verify($shuffler)->shuffle($this->greaterThan(10));
        /*                                ^ the constraint */
    }
}
            

Custom matchers are also supported.

Wildcard Parameters

  • For individual: PHPUnit anything() matcher
  • For all:
    
    Phake::when($mock)->methodName->thenReturn('bar');
    /*                            ^
    note the lack of parentheses here */
                        
  • For trailing parameters with default values:
    
    Phake::when($mock)
        ->methodName('foo', 'bar', Phake::ignoreRemaining())
        ->thenReturn('bar');
                        

Parameter Capturing


$this->bot->run();

$client->emit('irc.received', [
    /* array */ $message,
    /* object */ $write,
    /* object */ $connection,
    /* object */ $logger
]);

Phake::verify($client)->emit(
    'irc.sending.all',
    Phake::capture($allParams)
);
$this->assertSame($queue, $allParams[0]);
            

Matching Captured Parameters


Phake::verify($client)->emit(
    'irc.sending.all',
    Phake::capture($allParams)->when($this->isType('array'))
/*                              ^
uses the same matchers verify() supports */
);
            

PHPUnit: Mocking


$http = $this->getMockBuilder(ClientInterface::class)
    ->disableOriginalConstructor()
    ->disableOriginalClone()
    ->getMock();

// or:

$response = $this->getMock(
    ClientInterface::class,
    [],    // stub all methods
    [],    // no constructor arguments
    '',    // default mock class name
    false, // do not call original __construct()
    false  // do not call original __clone()
);
            

// Compare with:
$mock = Phake::mock(ClientInterface::class);
            

PHPUnit: Stubbing

Stubbing and verification APIs are conflated.


$http->expects($this->once()) // verification
     ->method('get')
     ->with('https://api.foo.com/things/1')
     ->willReturn($response); // stubbing
            

// Compare with:
Phake::when($http)
    ->get('https://api.foo.com/things/1')
    ->thenReturn($response);
Phake::verify($http)
    ->get('https://api.foo.com/things/1');
            

PHPUnit: Stubbing Multiple Calls


$mock->method('doThing')
     ->will($this->returnValueMap([
        ['arg1-1', 'arg1-2', 'ret1'],
        ['arg2-1', 'arg2-2', 'ret2'],
     ]));

// or:

$mock->method('doThing')
     ->will($this->returnCallback(
         function($arg1, $arg2) {
             // ...
         }
     );
            

// Compare with:
Phake::when($mock)
    ->doThing('arg1-1', 'arg1-2')
    ->thenReturn('ret1');
Phake::when($mock)
    ->doThing('arg2-1', 'arg2-2')
    ->thenReturn('ret2');
            

PHPUnit: Verifying Order


$mock->expects($this->at(0))
     ->method('doThing')
     ->with('arg1', 'arg2')
     ->willReturn('foo');

$mock->expects($this->at(1))
    // ...
    ;
            

// Compare with:
Phake::inOrder(
    Phake::verify($mock)
        ->doThing('arg1', 'arg2')
        ->thenReturn('foo'),
    Phake::verify($mock)
        // ...
);
            

Not Sure About Phake?

Here are some alternatives:

That's All, Folks

Feedback

Please rate my talk!

https://joind.in/talk/b501b

Also, check out the joind.in mobile apps!