Question

I learn TDD and I've started to use xSpec tool (language does not matter, but it's phpspec2 in my case). I write my first specification:

<?php

namespace spec\Mo\SpeechBundle\Controller;

use Doctrine\Common\Collections\Collection;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Mo\SpeechBundle\Repository\IdeaRepository;
use Mo\SpeechBundle\Repository\SpeechRepository;
use Mo\SpeechBundle\Entity\Idea;
use Mo\SpeechBundle\Entity\Speech;

class SpeechControllerSpec extends ObjectBehavior
{
    function let(SpeechRepository $speechRepository, IdeaRepository $ideaRepository, EngineInterface $templating)
    {
        $this->beConstructedWith($speechRepository, $ideaRepository, $templating);
    }

    function it_is_initializable()
    {
        $this->shouldHaveType('Mo\SpeechBundle\Controller\SpeechController');
    }

    function it_responds_to_show_action(EngineInterface $templating, Speech $speech, Response $response)
    {
        $templating
            ->renderResponse('MoSpeechBundle:Speech:show.html.twig', ['speech' => $speech])
            ->willReturn($response)
        ;

        $this->showAction($speech)->shouldBeAnInstanceOf('Symfony\Component\HttpFoundation\Response');
    }

    function it_responds_to_list_action(
        SpeechRepository $speechRepository,
        IdeaRepository $ideaRepository,
        EngineInterface $templating,
        Response $response
    )
    {
        $speeches = [new Speech()];
        $ideas = [new Idea()];

        $speechRepository->findAll()->willReturn($speeches);
        $ideaRepository->findAll()->willReturn($ideas);

        $templating
            ->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'ideas'))
            ->willReturn($response)
        ;

        $this->listAction()->shouldBeAnInstanceOf('Symfony\Component\HttpFoundation\Response');
    }

    function it_responds_list_by_idea_action(
        Idea $idea,
        SpeechRepository $speechRepository,
        IdeaRepository $ideaRepository,
        EngineInterface $templating,
        Response $response
    )
    {
        $speeches = [new Speech()];
        $ideas = [new Idea()];

        $speechRepository->findByIdea($idea)->willReturn($speeches);
        $ideaRepository->findAll()->willReturn($ideas);

        $templating
            ->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'idea', 'ideas'))
            ->willReturn($response)
        ;

        $this->listByIdeaAction($idea)->shouldBeAnInstanceOf('Symfony\Component\HttpFoundation\Response');
    }
}

For controller:

<?php

namespace Mo\SpeechBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Mo\SpeechBundle\Repository\IdeaRepository;
use Mo\SpeechBundle\Repository\SpeechRepository;
use Mo\SpeechBundle\Entity\Idea;
use Mo\SpeechBundle\Entity\Speech;

/**
 * Manages speeches.
 */
class SpeechController
{
    /**
     * @var \Mo\SpeechBundle\Repository\SpeechRepository
     */
    private $speechRepository;

    /**
     * @var \Mo\SpeechBundle\Repository\IdeaRepository
     */
    private $ideaRepository;

    /**
     * @var \Symfony\Bundle\FrameworkBundle\Templating\EngineInterface
     */
    private $templating;

    /**
     * @param \Mo\SpeechBundle\Repository\SpeechRepository $speechRepository
     * @param \Mo\SpeechBundle\Repository\IdeaRepository $ideaRepository
     * @param \Symfony\Bundle\FrameworkBundle\Templating\EngineInterface $templating
     */
    public function __construct(SpeechRepository $speechRepository, IdeaRepository $ideaRepository, EngineInterface $templating)
    {
        $this->speechRepository = $speechRepository;
        $this->ideaRepository = $ideaRepository;
        $this->templating = $templating;
    }

    /**
     * Shows speech.
     *
     * @param \Mo\SpeechBundle\Entity\Speech $speech
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function showAction(Speech $speech)
    {           
        return $this->templating->renderResponse('MoSpeechBundle:Speech:show.html.twig', compact('speech'));
    }

    /**
     * Shows list of speeches filtered by idea.
     *
     * @param \Mo\SpeechBundle\Entity\Idea $idea
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function listByIdeaAction(Idea $idea)
    {
        $speeches = $this->speechRepository->findByIdea($idea);
        $ideas = $this->ideaRepository->findAll();

        return $this->templating->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'idea', 'ideas'));
    }

    /**
     * Shows list of all speeches.
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function listAction()
    {
        $speeches = $this->speechRepository->findAll();
        $ideas = $this->ideaRepository->findAll();

        return $this->templating->renderResponse('MoSpeechBundle:Speech:list.html.twig', compact('speeches', 'ideas'));
    }
}

OK, now I'm sure that behavior of my controller is specified and I can move forward. But I have another problem.

I've used mock of repository for controller spec and now I want to write spec for repository itself:

<?php

namespace Mo\SpeechBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Mo\SpeechBundle\Entity\Idea;

class SpeechRepository extends EntityRepository
{
    /**
     * Finds all speeches by specified idea.
     *
     * @param \Mo\SpeechBundle\Entity\Idea $idea
     *
     * @return array
     */
    public function findByIdea(Idea $idea)
    {
        return $this
            ->createQueryBuilder('s')
            ->leftJoin('s.ideas', 'i')
            ->where('i = :idea')
            ->setParameters(compact('idea'))
            ->getQuery()
            ->getResult()
        ;
    }
}

But specs describe behavior, as I understood. How correctly test repository that it really returns what I need, in my case speeches by ideas.

Should I consider to create functional test with xUnit tool (PHPUnit in PHP world)? Or I wri te spec which describes that my repository correctly creates query? Or can I just use Behat for all app and don't pay attention to this problem.

Was it helpful?

Solution

I have spent a week for analysis of this question and found satisfactory answer.

The phpspec only specify behavior of our objects. Nothing more. We can't create functional tests with them.

So, we have two ways to test our functionality:

  1. Use PHPUnit to write functional tests for modules and system itself.
  2. Use Behat to describe features of our application.

PHPUnit, other similar framework and Behat have their pitfalls and strong sides.

What to use, can decide only a developer.

OTHER TIPS

I understand your predicament completely and I have done exactly the same thing in the past. I think that the basic answer to your question is that your business logic should be separate from any framework (infrastructure code), and so you should not be testing objects of the type 'Doctrine\ORM\EntityRepository'.

I think the best approach is the have another layer within your application which will hold your business logic, and this in turn could use adapters to pass messages back and forth from the 'Doctrine\ORM\EntityRepository' type objects. That way you can fully spec your business rules (including any adapters) without having to test doctrine type objects which should not be tested anyway as for the most part this is 3rd party code.

Doing things this way also makes it easier for you to change your business rules in future.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top