cult3

Creating and testing Doctrine Repositories

Oct 13, 2014

Table of contents:

  1. The separation of interfaces and implementations
  2. Recap of the UserRepository interface
  3. Creating the UserDoctrineORMRepository class
  4. Testing the Doctrine User Repository
  5. Conclusion

A couple of weeks ago we looked at the benefits of using Repositories in web applications.

We also laid the foundation for the User Repository by writing the initial interface.

There are many benefits to using Repositories in web applications. For example, coding to an interface, hiding the details of the storage provider and making it easier to test your code.

Testing code that depends on a Repository is really easy because we can simply inject a mocked Repository or in-memory implementation during our tests.

But how do you test the Repository classes themselves? In today’s tutorial I’m going to be looking at writing the first Doctrine Repository, setting up fixtures and writing Repository tests.

The separation of interfaces and implementations

An important characteristic of Repositories is the separation of the interface and the implementation.

Repositories are all about the selection of objects. By using a Repository you give the illusion that all objects in the application are held in memory.

It is therefore inevitable that you will need to use Repositories in your domain related code.

Repositories should have an interface and an implementation. We can depend on the interface within our domain related code because the interface is part of our application’s business logic.

However we don’t really care about the implementation and so the implementation of the Repository must live within the Infrastructure namespace. Theoretically we should be able to replace any Repository implementation.

This separation of the interface and the implementation is very important to realising the full benefit of using Repositories in your applications.

Recap of the UserRepository interface

Before we get into writing the Doctrine Repository implementation, first we will take a quick recap of the UserRepository methods.

/**
 * Return the next identity
 *
 * @return UserId
 */
public function nextIdentity();

The nextIdentity() method will return the an instance of UserId. Instead of relying on the database to produce unique id’s for my application, I will instead be using UUIDs. The Repository is the “collection” of entities and so it makes sense for the Repository to have the responsibility of creating a new id.

/**
 * Add a new User
 *
 * @param User $user
 * @return void
 */
public function add(User $user);

Next we have the add() method. The add() method should accept a fully formed User instance. The Repository will then deal with persisting the User to storage.

/**
 * Update an existing User
 *
 * @param User $user
 * @return void
 */
public function update(User $user);

Similarly the update() method should accept an instance of User. Storing the User object is the concern of the Repository and so we can deal with that in the specific implementation of the method.

/**
 * Find a user by their email address
 *
 * @param Email $email
 * @return User
 */
public function userOfEmail(Email $email);

/**
 * Find a user by their username
 *
 * @param Username $username
 * @return User
 */
public function userOfUsername(Username $username);

And finally we have two methods to retrieve a user by their email or their username. You could alternatively define a findBy() method that accepts an array of arguments. You would then use this single method for all types of queries. Personally I think being explicit with your methods is a better solution.

Now that we’ve got the UserRepository interface written, we can start to build out the Doctrine Repository implementation.

Creating the UserDoctrineORMRepository class

The first thing to do is to create the directory structure and namespace for the Repository implementations.

I’m going to create a new namespace called Repositories under the Infrastructure namespace. I may change this around in the future if I end up with a lot of Repositories, but I think this will be fine for now.

Here is the initial basic class:

<?php namespace Cribbb\Infrastructure\Repositories;

class UserDoctrineORMRepository implements UserRepository
{
    /**
     * @var EntityManager
     */
    private $em;

    /**
     * @var string
     */
    private $class;

    /**
     * Create a new UserDoctrineORMRepository
     *
     * @param EntityManager $em
     * @return void
     */
    public function __construct(EntityManager $em)
    {
        $this->em = $em;
        $this->class = "Cribbb\Domain\Model\Identity\User";
    }
}

There is a couple of things to notice so far in this file.

Firstly, we’ve created a plain PHP class called UserDoctrineORMRepository that implements the UserRepository interface.

A lot of the examples of Doctrine repositories that you will find on the internet will extends Doctrine’s EntityRepository.

This is great because it will allow your Repository to inherit a lot of the functionality that you will need.

However it also dilutes the benefit of using the UserRepository interface. If you are relying on Doctrine’s abstract methods, you are no longer coding to your explicit Domain interface.

Instead I will inject an instance of the EntityManager in through the constructor. I will also set the class property for the model. This is so we can easily call the default Repository to use those abstract Doctrine Repository methods internally to this class.

Now we can implement each of the methods.

The next identity method

The nextIdentity() method is very simple to implement because all we need to do is to return a new instance of UserId:

/**
 * Return the next identity
 *
 * @return UserId
 */
public function nextIdentity()
{
    return UserId::generate();
}

The UserId is responsible for creating UUIDs, and so we can use the generate() method to automatically create and return a new instance.

The add and update methods

The add() and update() methods are also fairly simple to implement:

/**
 * Add a new User
 *
 * @param User $user
 * @return void
 */
public function add(User $user)
{
    $this->em->persist($user);
    $this->em->flush();
}

/**
 * Update an existing User
 *
 * @param User $user
 * @return void
 */
public function update(User $user)
{
    $this->em->persist($user);
    $this->em->flush();
}

In each of these methods we can simply pass the instance of the User to the persist() method on the EntityManager and then call the flush() method.

If you remember back to How is Doctrine 2 different to Eloquent?, Doctrine uses a Unit of Work and so we have to explicitly tell it to send the data to the database.

The userOf methods

And finally we can implement the two userOf methods:

/**
 * Find a user by their email address
 *
 * @param Email $email
 * @return User
 */
public function userOfEmail(Email $email)
{
    return $this->em->getRepository($this->class)->findOneBy([
        'email' => $email->toString()
    ]);
}

/**
 * Find a user by their username
 *
 * @param Username $username
 * @return User
 */
public function userOfUsername(Username $username)
{
    return $this->em->getRepository($this->class)->findOneBy([
        'username' => $username->toString()
    ]);
}

Instead of reinventing the wheel, we can just delegate these methods to the default Doctrine Repository. This is internal to our implementation and so it is hidden from the outside world.

You will notice that I’ve not implemented a method to delete users. Deleting users is likely going to have business logic implications, and so we can look at that specific bit of functionality in isolation during a future tutorial.

Testing the Doctrine User Repository

Now that we have written the Repository implementation, we can test it to make sure it is all working correctly.

Many people will tell you that you should not hit the database during your tests. Personally I don’t agree with this statement as I think hitting the database is an inevitable part of testing an application.

You should obviously only hit the database in certain types of tests. For example, if you are injecting the Repository into a service class, it’s fine to mock the Repository.

Instead of actually hitting a MySQL database, we can instead hit an in-memory SQLite database. This will make running your tests a lot quicker and with less hassle of actually storing the data.

However problems might still creep in due to the differences between MySQL and SQLite. Just because your code passes your tests isn’t a guarantee that it will work on your production database.

So the first thing to do is to set up the test class:

<?php namespace Cribbb\Tests\Infrastructure\Repositories;

use Illuminate\Support\Facades\App;
use Cribbb\Infrastructure\Repositories\UserDoctrineORMRepository;

class UserDoctrineORMRepositoryTest extends \TestCase
{
    /** @var UserDoctrineORMRepository */
    private $repository;

    /** @var EntityManager */
    private $em;

    public function setUp()
    {
        parent::setUp();

        Artisan::call("doctrine:schema:create");

        $this->em = App::make("Doctrine\ORM\EntityManagerInterface");
        $this->repository = new UserDoctrineORMRepository($this->em);
    }
}

Before each test I’m going to need Doctrine to create the database schema. Using the Laravel Doctrine package, the database will automatically be set to SQLite as long as you have the default database set correctly in your testing config.

Next we can create a new instance of the EntityManager from the IoC container and we can then inject it into the UserDoctrineORMRepository

The next identity method test

The nextIdentity() method is really simple because we just need to ensure that a new UserId is returned:

/** @test */
public function should_return_next_identity()
{
    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\UserId', $this->repository->nextIdentity());
}

Doctrine Fixtures

Next we need to test the ability to return users that match a certain query. For these types of tests we need to seed the database with records so that we know the query is returning the correct results.

Instead of manually inserting the records before each test, we can instead use fixtures.

Doctrine provides a way of implementing fixtures that can be run before each test to seed the database.

Create a new namespace called Fixtures under the Repositories namespace and copy this file:

<?php namespace Cribbb\Tests\Infrastructure\Repositories\Fixtures;

use Cribbb\Domain\Model\Identity\User;
use Cribbb\Domain\Model\Identity\Email;
use Cribbb\Domain\Model\Identity\UserId;
use Cribbb\Domain\Model\Identity\Username;
use Doctrine\Common\Persistence\ObjectManager;
use Cribbb\Domain\Model\Identity\HashedPassword;
use Doctrine\Common\DataFixtures\FixtureInterface;

class UserFixtures implements FixtureInterface
{
    /**
     * Load the User fixtures
     *
     * @param ObjectManager $manager
     * @return void
     */
    public function load(ObjectManager $manager)
    {
        $id = UserId::generate();
        $email = new Email("name@domain.com");
        $username = new Username("username");
        $password = new HashedPassword("qwerty");

        $user = User::register($id, $email, $username, $password);

        $manager->persist($user);
        $manager->flush();
    }
}

Your fixture class should implement the FixtureInterface and should have the load(ObjectManager $manager) method.

Inside this method you can implement how you want the fixture(s) to be created. In the example above I’m creating a single user. However you could write a for loop to create multiple users.

Back in the UserDoctrineORMRepositoryTest we need to set up the fixture infrastructure:

public function setUp()
{
    parent::setUp();

    Artisan::call('doctrine:schema:create');

    $this->em = App::make('Doctrine\ORM\EntityManagerInterface');
    $this->repository = new UserDoctrineORMRepository($this->em);

    $this->executor = new ORMExecutor($this->em, new ORMPurger);
    $this->loader = new Loader;
    $this->loader->addFixture(new UserFixtures);
}

The last three lines of the setUp() method instantiate the ORMExecutor and the Loader. We also load the UserFixtures class we just implemented.

However we don’t need to actually load the fixtures for each test. We don’t need to touch the fixtures for the nextIdentity() method and so there is no point in loading them. Instead we can load the fixtures in each of the tests that we need them.

User of tests

Now that we have the fixtures in place we can write the tests for the user of methods:

/** @test */
public function should_find_user_by_username()
{
    $this->executor->execute($this->loader->getFixtures());

    $user = $this->repository->userOfUsername(new Username('username'));

    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\User', $user);
}

/** @test */
public function should_find_user_by_email()
{
    $this->executor->execute($this->loader->getFixtures());

    $user = $this->repository->userOfEmail(new Email('name@domain.com'));

    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\User', $user);
}

In both of these tests we first load the fixtures using the $this->executor instance.

Next we can use the Repository to find the fixture using the username and email address respectively. In each test we should be returned the User instance.

The add method

To test the add() method we can create a new User instance and pass it to the Repository:

/** @test */
public function should_add_new_user()
{
    $id = UserId::generate();
    $email = new Email('name@domain.com');
    $username = new Username('username');
    $password = new HashedPassword('qwerty');

    $this->repository->add(User::register($id, $email, $username, $password));

    $this->em->clear();

    $user = $this->repository->userOfUsername(new Username('username'));

    $this->assertEquals($id, $user->id());
    $this->assertEquals($email, $user->email());
    $this->assertEquals($username, $user->username());
}

We can then query for that user and then assert that the correct user is returned.

Notice how we didn’t run the fixtures and so the database is starting the test as empty.

The update method

For the update() method we don’t need to create a new user so we can simply load the fixture.

We can then retrieve the user by their username, update the username and then update the user by passing the object back to the repository.

We can then attempt to find the user by their new username and then assert that the correct user is returned.

Here is what the update method test looks like:

/** @test */
public function should_update_existing_user()
{
    $this->executor->execute($this->loader->getFixtures());

    $user = $this->repository->userOfUsername(new Username('username'));

    $user->updateUsername(new Username('new_username'));

    $this->repository->update($user);

    $this->em->clear();

    $user = $this->repository->userOfUsername(new Username('new_username'));

    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\User', $user);
}

Conclusion

Repositories are a very important component of a lot of different types of application, including Domain Driven Design applications. Repositories make it really easy to test any part of your application that relies on querying the database because it should be very easy to switch out a Repository implementation during your tests.

However, it is also very important that you test your Repositories! If your Repository implementation isn’t working correctly, none of your other tests really matter.

When using Repositories you should always rely on the interface and not the specific implementation. It should be very easy to switch out the Repository implementation by using a different concrete class that satisfies the same interface.

It can be tempting to inherit from an abstract Repository that provides a lot of the methods that you are going to need. Whilst this will make your Repository implementations easier to write, you are letting the Infrastructure leak into your application.

When testing Repositories it’s inevitable that you are going to hit the database. You can’t be sure that a query is working as you expect it to without running the code end-to-end.

Tests that aren’t specifically concerned with querying the database should still make mocked calls to the database, but don’t try to avoid touching the database at all costs. Your tests will be brittle and not very useful.

And finally, if you need to seed your database with data to ensure your queries are working correctly, use Fixtures as a way of populating data. Tests are hard enough to maintain as they are, you don’t need yet another thing to maintain as you continue to develop your application.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.