How to mock a non-deterministic method?

Viewed 144

Consider class with non-deterministic method:

class Foo
{
    public function getSome(): int
    {
        static $int = 0;
        return ++$int;
    }
}

How can I mock same behaviour using Mockery?

class Bar
{
    public function useFoo(Foo $foo)
    {
        echo $foo->getSome() . ", ";
        echo $foo->getSome() . ", ";
        echo $foo->getSome();
    }
}

$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('getSome')->andReturn(1);
$mock->shouldReceive('getSome')->andReturn(2);
$mock->shouldReceive('getSome')->andReturn(3);

// Should display 1, 2, 3
$bar = new Bar();
$bar->useFoo($mock);

Another example:

class Clock
{
    public function getTime(): int
    {
        return time();
    }
}

class Sleeper 
{
    public function sleep(int $seconds): void
    {
        sleep($seconds);
    }
}

How can I mock same behaviour using Mockery?

class Stoper
{
    private Clock $clock;
    private Sleeper $sleeper;

    public function __construct(Clock $clock, Sleeper $sleeper)
    {
        $this->clock = $clock;
        $this->sleeper = $sleeper;
    }

    public function measure(int $seconds): int
    {
        $start = $this->clock->getTime();
        $this->sleeper->sleep($seconds);

        return $this->clock->getTime();
    }
}

$clockMock = Mockery::mock(Clock::class);
$clockMock->shouldReceive('getTime')->andReturn(1000000001);
$clockMock->shouldReceive('getTime')->andReturn(1000000011);

$sleeperMock = Mockery::mock(Sleeper::class);
$sleeperMock->shouldReceive('sleep');

$stoper = new Stoper($clockMock, $sleeperMock);
$this->assertEquals(10, $stoper->measure(10));

Dirty solution

I figured out I can make dirty trick

class ClockCaller
{
    public function getTime(int $callNumber)
    {
        return (new Clock())->getTime();
    }
}

class Stoper
{
    private ClockCaller $clockCaller;
    private Sleeper $sleeper;

    public function __construct(ClockCaller $clockCaller, Sleeper $sleeper)
    {
        $this->clockCaller = $clockCaller;
        $this->sleeper = $sleeper;
    }

    public function measure(int $seconds): int
    {
        $start = $this->clockCaller->getTime(1);
        $this->sleeper->sleep($seconds);

        return $this->clockCaller->getTime(2);
    }
}

$clockMock = Mockery::mock(ClockCaller::class);
$clockMock->shouldReceive('getTime')->withArgs([1])->andReturn(1000000001);
$clockMock->shouldReceive('getTime')->withArgs([2])->andReturn(1000000011);

But it seems very nasty

2 Answers

The answer to your question is given by the Simple Example in the Mockery documentation. A temperature service that should return successively 10, 12 and 14 degrees can be mocked like this:

$service = Mockery::mock('service');
$service->shouldReceive('readTemp')
         ->times(3)
         ->andReturn(10, 12, 14);

So, for your Foo class:

$mock = Mockery::mock(Foo::class);
$mock->shouldReceive('getSome')
     ->times(3)
     ->andReturn(1, 2, 3);

and for your Clock:

$clockMock = Mockery::mock(Clock::class);
$clockMock->shouldReceive('getTime')
          ->times(2)
          ->andReturn(1000000001, 1000000011);

you can check the output with expectOutputString:

public function testFoo(){
    $this->expectOutputString("foo");
    echo "foo";
}

about sleep function, you can use \sleep() (global namespase), and override it.. also, you can use mocking built-in PHP functions

Related