Darius.V
Darius.V

Reputation: 938

Should I unit test classes which extend Sonata BaseEntityManager class?

Here is part of the code which extends BaseEntityManager:

namespace Vop\PolicyBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Persistence\ObjectRepository;
use Sonata\CoreBundle\Model\BaseEntityManager;

class AdditionalInsuredTypeManager extends BaseEntityManager
{
    /**
     * @param int $productId
     *
     * @return ArrayCollection
     */
    public function getProductInsuredTypes($productId = null)
    {
        $repository = $this->getRepository();

        $allActiveTypes = $repository->findAllActive();

        // other code
    }

    /**
     * @return AdditionalInsuredTypeRepository|ObjectRepository
     */
    protected function getRepository()
    {
        return parent::getRepository();
    }
}

And here I am trying to write a unit test:

public function testGetProductInsuredTypes()
    {
        $managerRegistry = $this->getMockBuilder(\Doctrine\Common\Persistence\ManagerRegistry::class)
            ->getMock();

        $additionalInsuredTypeManager = new AdditionalInsuredTypeManager(
            AdditionalInsuredTypeManager::class,
            $managerRegistry
        );

        $additionalInsuredTypeManager->getProductInsuredTypes(null);
    }

What are the problems:

Unable to find the mapping information for the class Vop\PolicyBundle\Entity\AdditionalInsuredTypeManager. Please check the 'auto_mapping' option (http://symfony.com/doc/current/reference/configuration/doctrine.html#configuration-overview) or add the bundle to the 'mappings' section in the doctrine configuration. /home/darius/PhpstormProjects/vop/vendor/sonata-project/core-bundle/Model/BaseManager.php:54 /home/darius/PhpstormProjects/vop/vendor/sonata-project/core-bundle/Model/BaseManager.php:153 /home/darius/PhpstormProjects/vop/src/Vop/PolicyBundle/Entity/AdditionalInsuredTypeManager.php:46 /home/darius/PhpstormProjects/vop/src/Vop/PolicyBundle/Entity/AdditionalInsuredTypeManager.php:21 /home/darius/PhpstormProjects/vop/src/Vop/PolicyBundle/Tests/Unit/Entity/AdditionalInsuredTypeManagerTest.php:22

I do not know how to fix this error, but this really has to do something with extending that BaseEntityManager I assume.

I see the error is caused by this line:

$repository = $this->getRepository();

I cannot even inject the repository from the constructor, because parent constructor has no such parameter.

There is very little amout of information about testing:

https://sonata-project.org/bundles/core/master/doc/reference/testing.html

Upvotes: 0

Views: 343

Answers (1)

dbrumann
dbrumann

Reputation: 17166

I can not tell you whether it's useful to test your repositories, neither can I give you pointers for your error other than that you should most likely not extend doctrine's entity manager. If anything use a custom EntityRepository or write a service in which you inject the EntityManager (or better the EntityRegistry):

class MyEntityManager
{
    private $entityManager;

    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function getProductInsuredTypes($productId = null)
    {
        $repository = $this->entityManager->getRepository(Product::class);

        $allActiveTypes = $repository->findAllActive();

        // other code
    }
}

What I can give you is an explanation how I approach testing repositories:

I think unit tests, especially with mocks, seem a bit wasteful. They only tie you to the current implementation and since anything of relevance is mocked out you will most likely not test any behavior.

What might be useful is doing functional tests where you provide a real database connection and then perform a query against the repository to see if it really returns the expected results. I usually do this for more complex queries or when using advanced doctrine features like native queries with a custom ResultsetMap. In this case you would not mock the EntityManager and instead would use Doctrine's TestHelper to create an in memory sqlite-database. This can look something like this:

protected $entityManager;

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

    $config = DoctrineTestHelper::createTestConfiguration();
    $config->setNamingStrategy(new UnderscoreNamingStrategy());
    $config->setRepositoryFactory(new RepositoryFactory());

    $this->entityManager = DoctrineTestHelper::createTestEntityManager($config);
}

The downside is, that you will manually have to register custom types and listeners which means the behavior might differ from your production configuration. Additionally you are still tasked with setting up the schema and providing fixtures for your tests. You will also most likely not use SQLite in production so this is another deviation. The benefit is that you will not have to deal with clearing your database between runs and can easily parallelize running tests, plus it's usually faster and easier than setting up a complete test database.

The final option is somewhat close to the previous one. You can have a test database, which you define in your parameters, environment variables or a config_test.yml, you can then bootstrap the kernel and fetch the entity manager from your DI-container. Symfony's WebTestCase can be used as a reference for this approach.

The downside is, that you will have to boot your kernel and make sure to have separate database setups for development, production and testing to ensure your test data does not mess up anything. You will also have to setup your schema and fixtures and additionally you can easily run into issues where tests are not isolated and start being flakey, e.g. when run in different order or when running only parts of your test suite. Obviously since this is a full integration test through your bootstrapped application the performance footprint compared to a unit test or smaller functional test is noticeably higher and application caches might give you additional headaches.

As a rule of thumb:

  • I trust Doctrine when doing the most basic kind of queries and therefore do not test repositories for simple find-methods.
  • When I write critical queries I prefer testing them indirectly on higher layers, e.g. ensure in an acceptance test that the page displays the information.
  • When I encounter issues with repositories or need tests on lower layers I start with functional tests and then move on to integration tests for repositories.

tl;dr

  • Unit tests for repositories are mostly pointless (in my opinion)
  • Functional tests are good for testing simple queries in isolation with a reasonable amount of effort, should you need them.
  • Integration tests ensure the most production like behavior, but are more painful to setup and maintain

Upvotes: 0

Related Questions