Search code examples
symfonyphp-7phpspec

How do you mock a function call that called multple times with changing parameters in PHPSpec?


I'm trying to create a test in PHPSpec for an function that calls a function on another object with differing parameters. So far, my attempts have caused several different errors so I'll outline what I have so far.

The most recent error:

- it should find all realm data
  method call:
    - fetch(LeagueOfData\Adapters\Request\RealmRequest:000000001212f67d000000001262e5c6 Object (
      'apiDefaults' => Array &0 (
          'region' => 'euw'
      )
      'format' => null
      'data' => null
      'query' => null
      'where' => Array &0
  ))
  on Double\AdapterInterface\P51 was not expected, expected calls were:
    fetch(exact(Double\RequestInterface\P50:000000001212f607000000001262e5c6 Object (
      'objectProphecy' => Prophecy\Prophecy\ObjectProphecy Object (*Prophecy*)

The PHPSpec file:

class JsonRealmsSpec extends ObjectBehavior
{
    function let(AdapterInterface $adapter, LoggerInterface $logger, RequestInterface $request)
    {
        // fetch called with multiple request objects but we're not interested in the exact data it returns yet.
        $adapter->fetch($request)->willReturn(['test data']);
        $this->beConstructedWith($adapter, $logger);
    }

    function it_should_find_all_realm_data()
    {
        $this->findAll()->shouldReturnArrayOfRealms();
    }


    function getMatchers()
    {
        return [
            'returnArrayOfRealms' => function($realms) {
                foreach ($realms as $realms) {
                    if (!$realm instanceof Realm) {
                        return false;
                    }
                }
                return true;
            }
        ];
    }
}

And the actual function being tested:

class JsonRealms implements RealmService
{
    const REGIONS = ['euw', 'eune', 'na'];

    private $source;
    private $log;
    private $realms;

    public function __construct(AdapterInterface $adapter, LoggerInterface $log)
    {
        $this->source = $adapter;
        $this->log = $log;
    }

    public function findAll() : array
    {
        $this->realms = [];
        foreach (self::REGIONS as $region) {
            $request = new RealmRequest(['region' => $region]);
            $response = $this->source->fetch($request);
            $this->realms[] = new Realm($realm['cdn'], $realm['v'], $region);
        }
        return $this->realms;
    }
}

I'm sure I'm probably missing something really obvious but for the life of me I cannot see it right now.


Solution

  • So it turns out I was missing something obvious, I was trying to solve it with a single mock call, as opposed to one for each case:

    function let(AdapterInterface $adapter, LoggerInterface $logger) {
        $request = new RealmRequest(['region' => 'euw']);
        $adapter->fetch($request)->willReturn([
            'cdn' => 'http://test.url/euw',
            'v' => '7.4.3'
        ]);
        $request = new RealmRequest(['region' => 'eune']);
        $adapter->fetch($request)->willReturn([
            'cdn' => 'http://test.url/eune',
            'v' => '7.4.3'
        ]);
        $request = new RealmRequest(['region' => 'na']);
        $adapter->fetch($request)->willReturn([
            'cdn' => 'http://test.url/na',
            'v' => '7.4.3'
        ]);
    }
    

    This appropriately sets up the mock adapter so that we can test the service is correctly creating the Realm objects.