Unit testing for Symfony2 repositories with PHPUnit

Quentin Pleplé – 2011

Here is the workflow I have to add a new function in an entity repository. Let’s say I have a blog and I want to get the most seen posts by counting the visits for each post. Here is the workflow I have:

  1. Write the functions structure in the repository
  2. Write the test structure test class
  3. Write the DQL query with the console
  4. Write the test in the test class
  5. Write the query in the repository

1 The usual way to do add a query in a repository to add a new function that query the database and return the results:

namespace Acme\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
 
class PostRepository extends EntityRepository {
    public function getMostSeen() {
        // query database and return results
    }
}

But this is hard to debug. I prefer to have for each query, two functions: one that build the query (that can easily be tested and debugged) and another one that execute that query and return result:

namespace Acme\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
 
class PostRepository extends EntityRepository {
    public function qbMostSeen() {
        return $this->createQueryBuilder('p')
                    -> // ...
    }
 
    public function getMostSeen() {
        return $this->qbMostSeen()
                    ->getQuery()
                    ->getResult();
    }
}

2 The test class will be in the Tests directory of the bundle:

namespace Acme\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class PostRespositoryTest extends WebTestCase {
    // ...
}

But we want to be able to instantiate PostRepository and for that we need the Entity Manager and a kernel (that we don’t have by default). So let’s add setUp() that will be executed before the tests:

namespace Acme\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class PostRespositoryTest extends WebTestCase {
    private $repo;
 
    public function setUp() {
        $kernel = static::createKernel();
        $this->repo = $kernel->boot();
        $this->repo = $kernel->getContainer()
                             ->get('doctrine.orm.entity_manager')
                             ->getRepository('AcmeBlogBundle:Post');
    }
}

We add our tests:

public function testMostSeen()
{
    $this->assertEquals(
        $this->repo->qbMostSeen()->getDql(),
        "..." // the DQL query
    );
 
    $this->assertNotEquals(0, count($this->repo->getMostSeen()));
}

You can add your tests on either the query or the results.


3 Write the DQL. Let’s write the DQL step by step testing each step with the console. Here, we get all the posts:

app/console doctrine:query:dql --max-result=5 "SELECT p.title FROM Acme\BlogBundle\Entity\Post p"

then we add one by one parts of our query, testing on our database to check the result every time. A left join to get all the visits:

SELECT p.title, v.id
FROM Acme\BlogBundle\Entity\Post p
LEFT JOIN p.visits v

then we add the visit count:

SELECT p.title, COUNT(v) AS visit_count
FROM Acme\BlogBundle\Entity\Post p
LEFT JOIN p.visits v
GROUP BY p.title

and we order by visit count:

SELECT p.title, COUNT(v) AS visit_count
FROM Acme\BlogBundle\Entity\Post p
LEFT JOIN p.visits v
GROUP BY p.title
ORDER BY visit_count DESC

Now that we are happy with the query, we can finish to write our tests.


4 Just insert the DQL in the test:

$this->assertEquals(
    $this->repo->qbMostSeen()->getDql(),
    "SELECT p.title, COUNT(v) AS visit_count FROM Acme\BlogBundle\Entity\Post p LEFT JOIN p.visits v GROUP BY p.title ORDER BY visit_count DESC"
);

5 Finally, write the query until PHPUnit is green!

namespace Acme\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
 
class PostRepository extends EntityRepository {
    public function qbMostSeen() {
        return $this->createQueryBuilder('p')
                    ->select('p.title', 'COUNT(v) AS visit_counts')
                    ->leftJoin('p.visits', 'v')
                    ->orderBy('visit_counts', 'DESC');
    }
 
    public function getMostSeen() {
        return $this->qbMostSeen()
                    ->getQuery()
                    ->getResult();
    }
}

Pro tip

You might want to add the alias dql to your shell environment (.bash_profile, .zsh/zshaliases or whatever):

alias dql="app/console doctrine:query:dql"

so you can just run in your shell:

dql "SELECT p.title FROM Acme\BlogBundle\Entity\Post p"
Feel free to ask questions right here ↓