Mocking Generators in phpunit

01 Nov 2017 in PHP, phpunit

To mock a method call returning a generator one can utilize phpunit's returnCallback() method.

Suppose a class Foo that gets some data from a Provider class. The Provider class interacts with a database.

<?php
class Foo {
  private $provider;
  function __construct(Provider $provider) {
    $this->provider = $provider;
  }
  function bar() {
    $data = $this->provider->get();
    // ...do something with data...
    return $data;
  }
}

class Provider {
  private $pdo;
  function __construct(PDO $pdo) {
    $this->pdo = $pdo;
  }
  function get() {
    $stmt = $this->pdo->prepare(
      "SELECT something FROM table"
    );
    $stmt->execute();
    while(($row = $stmt->fetch())) {
      yield $row;
    }
  }
}

To unit test the Foo class, one can write the following:

<?php
class FooTest extends \PHPUnit_Framework_TestCase {
  private $sut;
  private $provider;
  function setup() {
    $this->provider = $this
      ->getMockBuilder(Provider::class)
      ->disableOriginalConstructor()
      ->setMethods(["get"])
      ->getMock();
    $this->sut = new Foo($this->provider);
  }
  function testBar() {
    // TODO: mock Provider::get() method
    $actual = $this->sut->bar();
    // ...assertions to follow...
  }
}

Because a generator returns an Iterator instead of an array, you can't mock Provider::get() with something like phpunit's returnValue(). Instead you can take advantage of phpunit's returnCallback() like below:

  // ...snip...
  function testBar() {
    $this->provider
         ->expects($this->any())
         ->method("get")
         ->will($this->returnCallback(function () {
            $data = [
                // mock values
            ];
            foreach ($data as $e) {
                // return a generator
                yield $e;
            }
         }));
  // ...snip...