Coder Social home page Coder Social logo

webfactory / doctrine-orm-test-infrastructure Goto Github PK

View Code? Open in Web Editor NEW
20.0 11.0 5.0 322 KB

Provides utils to create a test infrastructure for Doctrine ORM entities.

License: MIT License

PHP 100.00%
php doctrine database testing phpunit doctrine-orm

doctrine-orm-test-infrastructure's Introduction

doctrine-orm-test-infrastructure

Tests

This library provides some infrastructure for tests of Doctrine ORM entities, featuring:

  • configuration of a SQLite in memory database, compromising well between speed and a database environment being both realistic and isolated
  • a mechanism for importing fixtures into your database that circumvents Doctrine's caching. This results in a more realistic test environment when loading entities from a repository.

We use it to test Doctrine repositories and entities in Symfony applications. It's a lightweight alternative to the heavyweight functional tests suggested in the Symfony documentation (we don't suggest you should skip those - we just want to open another path).

In non-application bundles, where functional tests are not possible, it is our only way to test repositories and entities.

Installation

Install via composer (see http://getcomposer.org/):

composer require --dev webfactory/doctrine-orm-test-infrastructure

Usage

<?php

use Doctrine\ORM\EntityManagerInterface;
use Entity\MyEntity;
use Entity\MyEntityRepository;
use PHPUnit\Framework\TestCase;
use Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure;

class MyEntityRepositoryTest extends TestCase
{
    private ORMInfrastructure $infrastructure;
    private MyEntityRepository $repository;

    protected function setUp(): void
    {
        /*
           This will create an in-memory SQLite database with the necessary schema
           for the MyEntity entity class and and everything reachable from it through
           associations.
        */
        $this->infrastructure = ORMInfrastructure::createWithDependenciesFor(MyEntity::class);
        $this->repository = $this->infrastructure->getRepository(MyEntity::class);
    }

    /**
     * Example test: Asserts imported fixtures are retrieved with findAll().
     */
    public function testFindAllRetrievesFixtures(): void
    {
        $myEntityFixture = new MyEntity();

        $this->infrastructure->import($myEntityFixture);
        $entitiesLoadedFromDatabase = $this->repository->findAll();

        /* 
            import() will use a dedicated entity manager, so imported entities do not
            end up in the identity map. But this also means loading entities from the
            database will create _different object instances_.

            So, this does not hold:
        */
        // self::assertContains($myEntityFixture, $entitiesLoadedFromDatabase);

        // But you can do things like this (you probably want to extract that in a convenient assertion method):
        self::assertCount(1, $entitiesLoadedFromDatabase);
        $entityLoadedFromDatabase = $entitiesLoadedFromDatabase[0];
        self::assertSame($myEntityFixture->getId(), $entityLoadedFromDatabase->getId());
    }

    /**
     * Example test for retrieving Doctrine's entity manager.
     */
    public function testSomeFancyThingWithEntityManager(): void
    {
        $entityManager = $this->infrastructure->getEntityManager();
        // ...
    }
}

Migrating to attribute-based mapping configuration

The ORMInfrastructure::createWithDependenciesFor() and ``ORMInfrastructure::createOnlyFor()` methods by default assume that the Doctrine ORM mapping is provided through annotations. This has been deprecated in Doctrine ORM 2.x and is no longer be supported in ORM 3.0.

To allow for a seamless transition towards attribute-based or other types of mapping, a mapping driver can be passed when creating instances of the ORMInfrastructure.

If you wish to switch to attribute-based mappings, pass a new \Doctrine\ORM\Mapping\Driver\AttributeDriver($paths), where $paths is an array of directory paths where your entity classes are stored.

For hybrid (annotations and attributes) mapping configurations, you can use \Doctrine\Persistence\Mapping\Driver\MappingDriverChain. Multiple mapping drivers can be registered on the driver chain by providing namespace prefixes. For every namespace prefix, only one mapping driver can be used.

Testing the library itself

After installing the dependencies managed via composer, just run

vendor/bin/phpunit

from the library's root folder. This uses the shipped phpunit.xml.dist - feel free to create your own phpunit.xml if you need local changes.

Happy testing!

Changelog

1.5.0 -> 1.5.1

  • Clear entity manager after import to avoid problems with entities detected by cascade operations (#23)
  • Use separate entity managers for imports to avoid interference between import and test phase (#2)
  • Deprecated internal class \Webfactory\Doctrine\ORMTestInfrastructure\MemorizingObjectManagerDecorator as it is not needed anymore: there are no more selective detach() calls`after imports

1.4.6 -> 1.5.0

  • Introduced ConnectionConfiguration to explicitly define the type of database connection (#15)
  • Added support for simple SQLite file databases via FileDatabaseConnectionConfiguration; useful when data must persist for some time, but the connection is reset, e.g. in Symfony's Functional Tests

Create file-backed database:

$configuration = new FileDatabaseConnectionConfiguration();
$infrastructure = ORMInfrastructure::createOnlyFor(
    MyEntity::class,
    $configuration
);
// Used database file:
echo $configuration->getDatabaseFile();

1.4.5 -> 1.4.6

  • Ignore associations against interfaces when detecting dependencies via ORMInfrastructure::createWithDependenciesFor to avoid errors
  • Exposed event manager and created helper method to be able to register entity mappings

Register entity type mapping:

$infrastructure->registerEntityMapping(EntityInterface::class, EntityImplementation::class);

Do not rely on this "feature" if you don't have to. Might be restructured in future versions.

1.4.4 -> 1.4.5

  • Fixed bug #20: Entities might have been imported twice in case of bidirectional cascade
  • Deprecated class Webfactory\Doctrine\ORMTestInfrastructure\DetachingObjectManagerDecorator (will be removed in next major release)

1.4.3 -> 1.4.4

  • Improved garbage collection
  • Dropped support for PHP < 5.5
  • Officially support PHP 7

Known Issues

Please note that apart from any open issues in this library, you may stumble upon any Doctrine issues. Especially take care of it's known sqlite issues.

Credits, Copyright and License

This package was first written by webfactory GmbH (Bonn, Germany) and received contributions from other people since then.

webfactory is a software development agency with a focus on PHP (mostly Symfony). If you're a developer looking for new challenges, we'd like to hear from you!

Copyright 2012 – 2024 webfactory GmbH, Bonn. Code released under the MIT license.

doctrine-orm-test-infrastructure's People

Contributors

dlundgren avatar kylekatarnls avatar maltewunsch avatar matthimatiker avatar mpdude avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

doctrine-orm-test-infrastructure's Issues

Ability to (auto)load entity configuration via XML/YML files

E.g. in the FOSUserBundle, one inherits from a Doctrine User entity that is configured via an XML file instead of annotations like we expect in the doctrine-orm-test-infrastructure. For now, these configuration files are ignored, which leads to errors like "unrecognized field: username".

Duplicating the configuration in annotation form is no workaround, as this leads to other errors (field is already defined).

It would be great to allow passing configuration files to ORMInfrastructure::create*. Even better, but not urgent, would be an auto-discover of these configuration files.

Entity imported twice in case of bi-directional cascade

Entities are imported twice, when there is a bi-directional cascade configuration:

// [...]
class List {
    // [...]
    /**
     * @OneToMany(targetEntity="Item", mappedBy="list", cascade={"all"})
     * @var Collection of Item objects
     */
     private $items;
}

// [...]
class Item {
    // [...]
    /**
     * @ManyToOne(targetEntity="List", inversedBy="items", fetch="EAGER", cascade={"persist"})
     * @JoinColumn(referencedColumnName="id")
     * @var List
     */
    private $list;
}

// [...]
$infrastructure->import($item)

This is caused by \Webfactory\Doctrine\ORMTestInfrastructure\Importer::importFromCallback() in combination with \Webfactory\Doctrine\ORMTestInfrastructure\DetachingObjectManagerDecorator, which executes flush() twice:

  • flush()
  • detach imported entities
  • flush()

Driver exposes all entity class names

At the moment, the used driver exposes the names of all known entities via \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver::getAllClassNames().
An entity is "known", if it was already loaded or if it is located in the same directory as a simulated entity file.

The driver should only expose the entities that have been explicitly simulated.

Improve detaching entities after import

Given two Entities:

/** @ORM\Entity */
class A {
    /** @ORM\OneToMany(targetEntity="B", cascade={"persist"}) */
    private $bs;

    public function __construct()
    {
        $this->bs = new ArrayCollection();
    }

    public function addB(B $b)
    {
        $this->bs->add($b);
    }
}

/** @ORM\Entity */
class B {
    /** @ORM\ManyToOne(targetEntity="A") */
    private $a;
}

... and a test starting als follows:

$this->infrastructure = ORMInfrastructure::createWithDependenciesFor(A::class);
$a = new A();
$a->add(new B());
$this->infrastructure->import($a);

... then the following:

$this->infrastructure->getEntityManager()->flush();

causes an error: Doctrine finds a "new" entity of class A through the relation B#a.

This is because \Webfactory\Doctrine\ORMTestInfrastructure\ORMInfrastructure::import() causes only the passed entities to be detached, in this case $a. $b remains in Doctrine's identity map and the ->flush() will affect it.

I don't think of this as a problem the tester should have to look out for on their own, but I want to hide the problem in the import process. To improve the detaching, we could either

  • simply call \Doctrine\ORM\EntityManager::clear(), detaching all entities
  • or put some work in detecting associated entities with persist={"cascade"} (and "all" or whatever)

Being lazy, I'd prefer the first idea. This might rise some weird bugs as discussed in #2, but as far as I understand: only when setup and test phase are not clearly separated. And I don't think this is a scenario we need to support - @Matthimatiker ?

Add a way to assert no further queries are executed

In a test, I'd like to make sure that associations have been loaded eagerly.

I could probably use reflection to access the entity field containing the association from my test class, asserting that it contains an initialized collection – but that seems really ugly.

Another way of asserting the desired behavior would be to fetch the entity, then tell the ORMInfrastructure that I do not expect any further database queries and then invoke some method on the entity that would trigger loading the collection.

Thoughts?

Different behavior if same entity manager is used for import and test logic

Given:

  • Model with unidirectional Many to Many relation
  • CASCADE configured on Doctrine level (not in database)

Expected behavior:

  • Deletion of parent entity automatically removes all associated child entities

It was observed that the order of entity deletions is not guaranteed in that case.
This can lead to bugs where the Doctrine tries to delete a child entity first and a foreign key is violated.

The deletion order is determined by the \Doctrine\ORM\Internal\CommitOrderCalculator, which does not define an absolute order for that case. Instead, the order depends on the order in which the metadata is assigned to the CommitOrderCalculator. That depends on the internal behavior of previous operations that are executed on the entity manager.

Possible solutions:

  • clear() entity manager after import
    • may lead to weird bugs if setup and test phase is not clearly separated and additional imports are executed between test code
  • use different entity managers for import and test
    • no easy solution if Importer is used separately, without Infrastructure

Automatically determine and setup dependencies

Automatically determine the entities that the tested entity depends on.
Setup these entities automatically as it can be assumed that the entity under test needs them to be operational.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.