Question

I'm still fresh in phpspec but usually I'm finding a solution when I struggle with something but this one is tough.

I've tried many different approaches and I haven't found a solution. I'm using Symfony2.

I have a class that I want to test:

class MyClass
{

    public function getDataForChildren(MyObject $object)
    {
        foreach ($object->getChildren() as $child) {
            $query = \json_decode($child->getJsonQuery(), true);
            $data = $this->someFetcher->getData($query);
            $child->setData($data);
        }
        return $object;
    }

}

And here's how look my spec class:

class MyClassSpec
{

    function let(SomeFetcher $someFetcher)
    {
        $this->beConstructedWith($someFetcher);
    }

    function it_is_initializable()
    {
        $this->shouldHaveType('MyClass');
    }

    function it_should_get_data_for_children_and_return_object(
        MyClass $object,
        MyClass $child, // it means that MyClass has a self-reference to MyClass
        $someFetcher
    )
    {
        $query = '{"id":1}';

        $returnCollection = new ArrayCollection(array($child));

        $object->getChildren()->shouldBeCalled()->willReturn($returnCollection);

        $child->getJsonQuery()->shouldBeCalled()->willReturn($query);

        $someFetcher->getData($query)->shouldBeCalled();

        $this->getDataForChildren($object);
    }

}

And after running phpspec I'm getting this error:

warning: json_decode() expects parameter 1 to be string, object given in

I have no idea how to solve this problem. If anyone has a clue, please help.

Was it helpful?

Solution

This is a common stumbling block with PhpSpec, the declaration:

   MyClass $child

means that a Collaborator object of $child will be set up with the same interface of MyClass. When child->getJsonQuery() is called in the SUT (class you're testing), it will return a MethodProphecy not the string you expect it to return.

What you want to say is that your ArrayCollection will contain not $child itself (which is a Collaborator object), but the real object that the collaborator is wrapped around. You do it like this:

$returnCollection = new ArrayCollection(array($child->getWrappedObject()));

In addition, you should not be using (i.e. is is superfluous) both shouldBeCalled() and willReturn() on the same Collaborator, one or the other is sufficient. If you've specified what the collabrator will return, it is clear that it is going to be called witin the SUT. shouldBeCalled() should be used in the "assert" part of the test in order to confirm that the Collaborator was called with the expected arguments, or at the right time.

Your final SUT and spec should look something like this:

   class MyClass
   {

        /**
         * @var SomeFetcher
         */
        private $someFetcher;

        public function getDataForChildren(MyObject $object)
        {
            foreach ($object->getChildren() as $child) {
                $query = \json_decode($child->getJsonQuery(), true);
                $data = $this->someFetcher->getData($query);
                $child->setData($data);
            }
            return $object;
        }

        public function getJsonQuery()
        {
        }

        public function setData()
        {
        }

        public function __construct(SomeFetcher $someFetcher)
        {
            $this->someFetcher = $someFetcher;
        }
    }

class MyClassSpec extends ObjectBehavior
{

    function let(SomeFetcher $someFetcher)
    {
        $this->beConstructedWith($someFetcher);
    }

    function it_should_get_data_for_children_and_return_object(
        MyObject $object,
        MyClass $child, // it means that MyClass has a self-reference to MyClass
        SomeFetcher $someFetcher
    )
    {
        $query = '{"id":1}';

        $returnCollection = new ArrayCollection(array($child->getWrappedObject()));

        $object->getChildren()->willReturn($returnCollection);

        $child->getJsonQuery()->willReturn($query);
        $child->setData(Argument::any())->shouldBeCalled();

        $someFetcher->getData(array('id' => 1))->shouldBeCalled();

        $this->getDataForChildren($object);
    }

}

Also, the line

$query = \json_decode($child->getJsonQuery(), true);

Will produce a associated array in $query, i.e. array('id' => 1) (this is what the second 'true' argument to json_encode stipulates), therefore you'd expect $someFetcher->getData() to be called with the latter, hence:

$someFetcher->getData(array('id' => 1))->shouldBeCalled();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top