Doctrine ORM Repository Unit Tests

Last time, in the Doctrine ORM Repository Hydration article, we discussed how to use the Doctrine ORM to separate our SQL code into a repository layer. This enabled us to pass a strongly typed DTO to the UI layer as opposed to stringly-typed arrays.

This article will be focused on designing phpunit tests for the previous article. We will focus our efforts on these four repository implementations:

  1. Manual Hydrator
  2. Result Set Mapping Hydrator
  3. Custom Hydrator
  4. DTO Hydrator

The source code and tests can be found on Github.

Since we know that each of the four classes implements the CompanyRepositoryInterface, we can write our tests while targeting this interface. Code against an interface; test against an interface. This will allow us to keep our tests DRY.

interface CompanyRepositoryInterface
{
    public function getCompanyStats(int $companyId): CompanyStatsDTO;
}

The AbstractDoctrineCompanyRepositoryTest class defines a single abstract method to get the specific repository implementation we want to test. The abstract method, getCompanyRepository(), uses the new PHP 7 strict return type declarations to ensure that we are testing against a class that conforms to the CompanyRepositoryInterface.

The most basic test that we can write involves an empty database with zero companies and employees. When calling getCompanyStats() we expect there to be zero active and zero inactive employees.

abstract class AbstractDoctrineCompanyRepositoryTest extends RepositoryTestCase
{
    /** @var CompanyRepositoryInterface */
    protected $companyRepository;

    public function setUp()
    {
        parent::setUp();
        $this->companyRepository = $this->getCompanyRepository();
    }

    abstract protected function getCompanyRepository(): CompanyRepositoryInterface;

    public function testGetCompanyStatsWithNoCompanyAndNoEmployees()
    {
        $companyStats = $this->companyRepository->getCompanyStats(1);

        $this->assertSame(0, $companyStats->totalActiveEmployees());
        $this->assertSame(0, $companyStats->totalInactiveEmployees());
    }
}

Next, we need to write the specific tests for the four repository implementations mentioned at the beginning of this article.

class ManualHydratorCompanyRepositoryTest extends AbstractDoctrineCompanyRepositoryTest
{
    protected function getCompanyRepository(): CompanyRepositoryInterface
    {
        return new ManualHydratorCompanyRepository($this->entityManager);
    }
}
class ResultSetMappingCompanyRepositoryTest extends AbstractDoctrineCompanyRepositoryTest
{
    protected function getCompanyRepository(): CompanyRepositoryInterface
    {
        return new ResultSetMappingCompanyRepository($this->entityManager);
    }
}
class CustomHydratorCompanyRepositoryTest extends AbstractDoctrineCompanyRepositoryTest
{
    protected function getCompanyRepository(): CompanyRepositoryInterface
    {
        return new CustomHydratorCompanyRepository($this->entityManager);
    }
}
class DTOHydratorCompanyRepositoryTest extends AbstractDoctrineCompanyRepositoryTest
{
    protected function getCompanyRepository(): CompanyRepositoryInterface
    {
        return new DTOHydratorCompanyRepository($this->entityManager);
    }
}

After running the phpunit tests, we have 4 tests that pass with 8 assertions.

$ vendor/bin/phpunit tests/RepositoryHydration/Repository
PHPUnit 5.2.10 by Sebastian Bergmann and contributors.

....                                                                4 / 4 (100%)

Time: 66 ms, Memory: 8.00Mb

OK (4 tests, 8 assertions)

The next meaningful test that we can write involves adding records to the database for a single company containing three employees, two of which are active. We split the body of the unit test method into three blocks using the Arrange – Act – Assert (AAA) strategy that Kent Beck describes in his Test-Driven Development book. This is also referred to Given – When – Then in some circles.

public function testGetCompanyStats()
{
    $companyId = $this->getCompanyIdForStatsTest();

    $companyStats = $this->companyRepository->getCompanyStats($companyId);

    $this->assertSame(2, $companyStats->totalActiveEmployees());
    $this->assertSame(1, $companyStats->totalInactiveEmployees());
}

Above, the Assemble block is delegated to a separate private method, getCompanyIdForStatsTest(), which initializes the database with the conditions defined earlier (2 active, and 1 inactive employee). The Act block simply calls getCompanyStats(), and the Assert section contains two assertions for the employee totals. This concept will help organize your tests into simple meaningful units.

Below you can see the code that will setup a single Company containing 3 employees. It delegates the creation of the Entity object via a getDummyXX() method. This strategy will help decouple your tests from the creation of entities. An indicator of coupled test code is the use of the new keyword to create Entities in your tests.

We leverage $this->entityManager to store these new Entities to the database with persist, flush, and clear. Clearing the EntityManager is recommended after assembling your unit test. This will detach any Entities that it currently manages. It will also clear the Identity Map, which will keep any future repository method calls in the Act block from being tainted by this first-level-cache. Doctrine uses the Identity Map pattern (for optimization) to keep a reference to Entities when you fetch them from the database.

private function getCompanyIdForStatsTest(): int
{
    $company = $this->getDummyActiveCompany();

    $employee1 = $this->getDummyActiveEmployee($company, 1);
    $employee2 = $this->getDummyActiveEmployee($company, 2);
    $employee3 = $this->getDummyInactiveEmployee($company, 3);

    $this->entityManager->persist($company);
    $this->entityManager->persist($employee1);
    $this->entityManager->persist($employee2);
    $this->entityManager->persist($employee3);
    $this->entityManager->flush();
    $this->entityManager->clear();

    return $company->getId();
}

For clarity, below is the getDummyActiveCompany() method.

private function getDummyActiveCompany(): Company
{
    $company = new Company;
    $company->setName('Acme');
    $company->setIsActive(true);

    return $company;
}

Summary

We covered a few best practices that are helpful in my projects. Testing against an interface can save you time and allow you to focus on the interface requirements. Arrange – Act – Assert keeps your unit test methods clean and consistent. Clearing the EntityManager avoids side-effects in your repository layer tests.

Leave a Reply

Your email address will not be published. Required fields are marked *