Search code examples
unit-testingzend-framework2phpunit

PHPUnit: how to mock multiple method calls with multiple arguments and no straight order?


I need to test following function:

[...]
public function createService(ServiceLocatorInterface $serviceManager)
{

    $firstService = $serviceManager->get('FirstServiceKey');
    $secondService = $serviceManager->get('SecondServiceKey');

    return new SnazzyService($firstService, $secondService);
}
[...]

I know, I might test it this way:

class MyTest extends \PHPUnit_Framework_TestCase
{
    public function testReturnValue()
    {
        $firstServiceMock = $this->createMock(FirstServiceInterface::class);
        $secondServiceMock = $this->createMock(SecondServiceInterface::class);

        $serviceManagerMock = $this->createMock(ServiceLocatorInterface::class);

        $serviceManagerMock->expects($this->at(0))
             ->method('get')
             ->with('FirstServiceKey')
             ->will($this->returnValue($firstService));

        $serviceManagerMock->expects($this->at(1))
             ->method('get')
             ->with('SecondServiceKey')
             ->will($this->returnValue($secondServiceMock));

        $serviceFactory = new ServiceFactory($serviceManagerMock);

        $result = $serviceFactory->createService();
    }
    [...]

or

[...]
public function testReturnValue()
{
    $firstServiceMock = $this->createMock(FirstServiceInterface::class);
    $secondServiceMock = $this->createMock(SecondServiceInterface::class);

    $serviceManagerMock = $this->createMock(ServiceLocatorInterface::class);

    $serviceManagerMock->expects($this->any())
         ->method('get')
         ->withConsecutive(
            ['FirstServiceKey'],
            ['SecondServiceKey'],
         )
         ->willReturnOnConsecutiveCalls(
            $this->returnValue($firstService),
            $this->returnValue($secondServiceMock)
         );

    $serviceFactory = new ServiceFactory($serviceManagerMock);

    $result = $serviceFactory->createService();
}
[...]

Both workes fine, but if I swap the ->get(xxx) lines in the createService function, both tests will fail. So, how do I have to change the testcases which doesn't need a specific sequenz for the parameters 'FirstServiceKey', 'SecondServiceKey, ...


Solution

  • You can try with willReturnCallback or willReturnMap strategy, as example of willReturnCallback:

    public function testReturnValue()
    {
        $firstServiceMock = $this->createMock(FirstServiceInterface::class);
        $secondServiceMock = $this->createMock(SecondServiceInterface::class);
    
        $serviceManagerMock = $this->createMock(ServiceLocatorInterface::class);
    
    
        $serviceManagerMock->expects($this->any())
            ->method('get')
            ->willReturnCallback(
                function ($key) use($firstServiceMock, $secondServiceMock) {
                    if ($key == 'FirstServiceKey') {
                        return $firstServiceMock;
                    }
                    if ($key == 'SecondServiceKey') {
                        return $secondServiceMock;
                    }
                    throw new \InvalidArgumentException; // or simply return;
                }
            );
    
        $serviceFactory = new ServiceFactory($serviceManagerMock);
    
        $result = $serviceFactory->createService();
    }
    

    Hope this help