phpspec / prophecy

Highly opinionated mocking framework for PHP 5.3+
MIT License
8.53k stars 241 forks source link

Supporting multiple predictions on the same prophecy #76

Open stof opened 10 years ago

stof commented 10 years ago

Currently, a MethodProphecy can only have a single delayed prediction (registered with should() and checked when the prophet checks predictions). Setting a new prediction will loose the previous one. To test multiple predictions, it is necessary to check them immediately with shouldHave().

Could it make sense to register multiple predictions on the same prophecy ?

everzet commented 9 years ago

I don't think so. But we could introduce compound predictions, like we have compound argument tokens: https://github.com/phpspec/prophecy/blob/master/spec/Prophecy/Argument/Token/LogicalAndTokenSpec.php

hanneskaeufler commented 9 years ago

I sometimes find the need for multiple predictions. Consider a case like:

// simple value object
class Entity {
    private $name;

    public function __construct($name) { $this->name = $name; }
    public function getName() { return $this->name; }
}

// system under test
// creates value object(s), sends them to collaborator
class Manager
{
    public function doStuff()
    {
        $entity1 = new Entity('first');
        $entity2 = new Entity('second');

        $this->collab->perform($entity1);
        $this->collab->perform($entity2);
    }

    public function __construct(Collaborator $collab) { $this->collab = $collab; }
}

To spec the doStuff method I intuitively wrote a test like

    function it_does_stuff_with_collaborator(Collaborator $collab)
    {
        $collab->perform(Argument::which('getName', 'first'))->shouldBeCalled();
        $collab->perform(Argument::which('getName', 'second'))->shouldBeCalled();

        $this->doStuff();
    }

which does not work.

Using a callback style argument matcher did not get me further for the same reason I guess?

    function it_does_stuff_with_collaborator(Collaborator $collab)
    {
        $collab->perform(Argument::that(function ($entity) {
            return $entity->name === 'first';
        }))->shouldBeCalled();
        $collab->perform(Argument::that(function ($entity) {
            return $entity->name === 'second';
        }))->shouldBeCalled();

        $this->doStuff();
    }

What do you mean checking predictions immediately @stof? Or does that pain point out an obvious flaw in my system under test? Even getting the entity from a factory type collaborator would not help if both calls to perform would act on the same instance.

stof commented 9 years ago

@hanneskaeufler your use case can be achieved if you use a spy to check predictions after the action (checking them immediately when you ask for them) instead of using a mock in which predictions are registered to be checked at the end of the test:


    function it_does_stuff_with_collaborator(Collaborator $collab)
    {
        $this->doStuff();

        $collab->perform(Argument::which('getName', 'first'))->shouldHaveBeenCalled();
        $collab->perform(Argument::which('getName', 'second'))->shouldHaveBeenCalled();
    }
xserrat commented 3 years ago

Hi @stof ,

Did you know if this feature request will be implemented soon?

I'm on a similar situation where I need to check consecutive calls in a method and check if the arguments passed are those I expect:


$collab->perform(
      Argument::that(
             function(MyEntity $entity) {
                  $this->assertSame('expectedvalue', $entity->getName());

                  return true;
             }
     )
)->shouldBeCalled();

$collab->perform(
      Argument::that(
             function(MyEntity $entity) {
                  $this->assertSame('anotherExpectedValue', $entity->getName());

                  return true;
             }
     )
)->shouldBeCalled();

I'm trying to do the same logic as the PhpUnit mock has about consecutive calls.

Do you know if there's another way to do it?

Thanks a lot!

stof commented 3 years ago

@xserrat no idea. this will be implemented when someone works on it. And I'm not working on it.

xserrat commented 3 years ago

@stof Thank you so much for your fast reply! I'll note that to try to implement it and open a pull request :)

tyteen4a03 commented 2 years ago

+1 - would be great to see.