12

I have a simple use case. I want to have a setUp method which will cause my mock object to return a default value:

$this->myservice
        ->expects($this->any())
        ->method('checkUniqueness')
        ->will($this->returnValue(true));

But then in some tests, I want to return a different value:

$this->myservice
        ->expects($this->exactly(1))
        ->method('checkUniqueness')
        ->will($this->returnValue(false));

I've used GoogleMock for C++ in the past and it had "returnByDefault" or something to handle that. I couldn't figure out if this is possible in PHPUnit (there is no api documentation and the code is difficult to read through to find what I want).

Now I can't just change $this->myservice to a new mock, because in setup, I pass it into other things that need to be mocked or tested.

My only other solution is that I lose the benefit of the setup and instead have to build up all of my mocks for every test.

Matt
  • 5,478
  • 9
  • 56
  • 95

3 Answers3

3

You could move the setUp() code into another method, which has parameters. This method gets then called from setUp(), and you may call it also from your test method, but with parameters different to the default ones.

cweiske
  • 30,033
  • 14
  • 133
  • 194
1

Continue building the mock in setUp() but set the expectation separately in each test:

class FooTest extends PHPUnit_Framework_TestCase {
  private $myservice;
  private $foo;
  public function setUp(){
    $this->myService = $this->getMockBuilder('myservice')->getMock();
    $this->foo = new Foo($this->myService);
  }


  public function testUniqueThing(){
     $this->myservice
        ->expects($this->any())
        ->method('checkUniqueness')
        ->will($this->returnValue(true));

     $this->assertEqual('baz', $this->foo->calculateTheThing());
  }

  public function testNonUniqueThing(){
     $this->myservice
        ->expects($this->any())
        ->method('checkUniqueness')
        ->will($this->returnValue(false));

     $this->assertEqual('bar', $this->foo->calculateTheThing());

  }


}

The two expectations will not interfere with each other because PHPUnit instantiates a new instance of FooTest to run each test.

bdsl
  • 288
  • 2
  • 9
0

Another little trick is to pass the variable by reference. That way you can manipulate the value:

public function callApi(string $endpoint):bool
{
    // some logic ...
}

public function getCurlInfo():array 
{
    // returns curl info about the last request
}

The above code has 2 public methods: callApi() that calls the API, and a second getCurlInfo()-method that provides information about the last request that's been done. We can mock the output of getCurlInfo() according to the arguments provided / mocked for callApi() by passing a variable as reference:

$mockedHttpCode = 0;
$this->mockedApi
    ->method('callApi')
    ->will(
        // pass variable by reference:
        $this->returnCallback(function () use (&$mockedHttpCode) {
            $args = func_get_args();
            $maps = [
                ['endpoint/x', true, 200],
                ['endpoint/y', false, 404],
                ['endpoint/z', false, 403],
            ];
            foreach ($maps as $map) {
                if ($args == array_slice($map, 0, count($args))) {
                    // change variable:
                    $mockedHttpCode = $map[count($args) + 1];
                    return $map[count($args)];
                }
            }
            return [];
        })
    );

$this->mockedApi
    ->method('getCurlInfo')
    // pass variable by reference:
    ->willReturn(['http_code' => &$mockedHttpCode]);

If you look closely, the returnCallback()-logic actually does the same thing as returnValueMap(), only in our case we can add a 3rd argument: the expected response code from the server.

Giel Berkers
  • 2,852
  • 3
  • 39
  • 58