Search code examples
phptestingbddphpspec

Testing call of multiple methods in phpspec


In the past i always stumbled across a certain problem with phpspec:

Lets assume i have a method which calls multiple methods on another object

class Caller {
    public function call(){
       $this->receiver->method1();
       ...
       $this->receiver->method2();
    }
}

In BDD i would first write a test which makes sure method1 will be called.

function it_calls_method1_of_receiver(Receiver $receiver){
    $receiver->method1()->shouldBeCalled();
    $this->call();
}

And then i would write the next test to assure method2 will be called.

function it_calls_method2_of_receiver(Receiver $receiver){
    $receiver->method2()->shouldBeCalled();
    $this->call();
}

But this test fails in phpspec because method1 gets called before method2. To satisfy phpspec i have to check for both method calls.

 function it_calls_method2_of_receiver(Receiver $receiver){
    $receiver->method1()->shouldBeCalled();
    $receiver->method2()->shouldBeCalled();
    $this->call();
}

My problem with that is, that it bloats up every test. In this example it's just one extra line but imagine a method which builds an object with a lot of setters. I would need to write all setters for every test. It would get quite hard to see the purpose of the test since every test is big and looks the same.

I'm quite sure this is not a problem with phpspec or bdd but rather a problem with my architecture. What would be a better (more testable) way to write this?

For example:

public function handleRequest($request, $endpoint){
    $endpoint->setRequest($request);
    $endpoint->validate();
    $endpoint->handle();
}

Here i validate if an request provides all necessary info for a specific endpoint (or throw an exception) and then handle the request. I choose this pattern to separate validating from the endpoint logic.


Solution

  • Prophecy, the mocking framework used by PhpSpec, is very opinionated. It follows the mockist approach (London School of TDD) which defends that we should describe one behaviour at a time.

    A mock is a test, so you want to keep one mock per test. You can mock all the calls, but that will not look elegant. The recommended approach is to separate the behaviour you are testing and select the mock you need for that behaviour, stubbing the rest of the calls. If you see yourself creating loads of stubs in one test that indicates feature envy — you should consider moving the behaviour to the callee, or add a man in the middle.

    Say you decide to go ahead and describe the code you have, without refactoring. If you are interested in the second call, as per your example, you should stub the other calls using willReturn, or similar. E.g. $endpoint->setRequest(Argument::type(Request::class))->willReturn() instead of shouldBeCalled().