phpspec / phpspec

SpecBDD Framework for PHP
http://www.phpspec.net
Other
1.87k stars 276 forks source link

Error when using immutable method on dependency, then using beConstructedWith() in spec #1448

Open toby-griffiths opened 10 months ago

toby-griffiths commented 10 months ago

I am configuring Symfony's immutable HttpClient in the constructor of a service. I have the spec set up to return a new instance of the client in the let() method, which works fine in spec that don't reconfigure the constructor.

However, if I attempt to reconfigure the constructor in an example then I get an error about the return type…

class ApiClientSpec extends ObjectBehavior
{
    public function let(
        HttpClientInterface $initialHttpClient,
        HttpClientInterface $httpClient,
        ResponseInterface $response,
    ): void {
        $this->beConstructedWith('https://api.co', $initialHttpClient);

        $initialHttpClient->withOptions(Argument::type('array'))->willReturn($httpClient);

        $initialHttpClient->request(Argument::in(['GET']), Argument::type('string'))->willReturn($response);

        $response->getContent()->willReturn('[]');
    }

    public function it_is_initializable(): void
    {
        $this->shouldHaveType(ApiClient::class);
    }

    public function it_should_provide_the_base_url_length(HttpClientInterface $initialHttpClient): void
    {
        $this->beConstructedWith('https://api.co', $initialHttpClient);

        $this
            ->getBaseUrlLength()
            ->shouldReturn(14);
    }
}

… results in …

[err:TypeError("Double\HttpClientInterface\HttpClientInterface\P3::withOptions(): Return value must be of type Double\HttpClientInterface\HttpClientInterface\P3, Double\HttpClientInterface\P1 returned")] has been thrown.

… however, if I inject the resulting client into the example, even if it's not used, it works just fine…

    // …

    public function it_should_provide_the_base_url_length(
        HttpClientInterface $initialHttpClient,
        // Keep this here, as without it the example fails with a return type error
        // @see https://github.com/phpspec/phpspec/issues/1448
        HttpClientInterface $httpClient,
    ): void {
        $this->beConstructedWith('https://api.co', $initialHttpClient);

        $this
            ->getBaseUrlLength()
            ->shouldReturn(14);
    }

Is this a known limitation?

toby-griffiths commented 10 months ago

Actually, I've just realised it doesn't matter whehter I update the constructor args in a example or not, it gives the same error. I this I may have been mislead by my assuption that the it_is_initializable() example would instantiate the object?!

So this example also gives the Return value must be of type Double\HttpClientInterface\P1, Double\HttpClientInterface\HttpClientInterface\P3 returned error as well…

    public function it_should_send_get_requests(
        HttpClientInterface $httpClient,
        ResponseInterface $response,
    ): void {
        $response->getContent()->willReturn('{"id": 123, "name": "Bob Bobbins"}');

        $this
            ->get('people/123')
            ->shouldReturn(['id' => 123, 'name' => 'Bob Bobbins']);

        $httpClient->request('GET', 'people/123')->shouldBeCalledOnce();
    }
ciaranmcnulty commented 10 months ago

Yes there is some semi-complex logic around when an object is instantiated, it may not be if the only assertion is about its type

I don't think this is a known issue

toby-griffiths commented 10 months ago

Ah, OK. Thanks @ciaranmcnulty. Would yo like me to leave this open for review then? I'm working OK by storing the initial collaborator instances in properties and using them in the examples.