PHPUnit: How to test Doctrine Repository method that returns void?

Viewed 528

I have a Doctrine repository class that can be used to persist a User, I don't want to check if the entity was really persisted or not, I just want to know what to do in this situation:

It's easy to test a repository method that has a return value. But in this case, I have nothing to do, and I'd like to cover 100% of the code, without making unsafe code that can break like use @addToAssertionCount.

<?php

namespace Domain\Repository;

use DateTime;
use Domain\Entity\User;
use Domain\Repository\Interfaces\UserRepositoryInterface;

class UserRepository extends Repository implements UserRepositoryInterface
{
    public function create(User $user): void
    {
        $user->setCreatedAt(new DateTime('now'));

        $this->getEntityManager()->persist($user);
    }
}

And a testing class for it:

<?php

namespace Domain\Repository;

use Doctrine\ORM\EntityManager;
use Domain\Entity\User;
use PHPUnit\Framework\TestCase;

class UserRepositoryTest extends TestCase
{
    private UserRepository $sut;

    public function setUp(): void
    {
        $entity_manager = $this->createMock(EntityManager::class);
        $this->sut = new UserRepository($entity_manager);
    }

    public function test_assert_create(): void
    {
        $user = $this->createMock(User::class);
        $this->sut->create($user);

        // What to assert??
    }
}

At this point, I don't know even what to assert, once the persist() method returns void, which I can't mock.

1 Answers

Focusing on 100% code coverage is not a good idea and encourages writing tests that have little to no value. What does that mean? The create method has two side effects: It changes the users creation date and persists it. You could test it like this:

final class UserRepositoryTest extends TestCase
{
    /**
     * @var EntityManager&MockObject
     */
    private EntityManager $entityManager;

    private UserRepository $sut;

    public function setUp(): void
    {
        $this->entityManager = $this->createMock(EntityManager::class);
        $this->sut           = new UserRepository($this->entityManager);
    }

    public function test_create_should_persist_entity(): void
    {
        $user = new User();
        $user->setCreatedAt(new DateTime('2000-01-01 12:15:30'));

        // validate that persist call was made
        $this->entityManager->expects(self::once())
            ->method('persist')
            ->with($user);

        $this->sut->create($user);

        // validate that creation date was set
        self::assertEqualsWithDelta(new DateTime('now'), $user->getCreatedAt(), 3);
    }
}

You could go even one step further and verify that the creation date was set before the persist call was made by using the callback constraint. But then, you're pretty much checking the implementation line by line in your test. That's how people end with tests that break all the time.

So, what to do instead? Focus on the purpose of the user repository: If you put something in, you should be able to get it out. This, however, requires that you use an actual entity manager. But you wrote, that you don't want to check that the entity was actually persisted. In that case, I would rather write no tests than the example I gave above.

Related