Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 Doctrine MongoDB rollback

I am trying to build a secure of set of tests with Symfony2, Doctrine and MongoDB.

What I need to do is to load a lot of fixtures when a test begin, and unload them once it ends. I thought of doing it with a transaction, but... I couldn't find documentation on how to do it with Doctrine and Mongo!

I found good documentation in the Doctrine docs regarding how to do transactions with the ORM, but not regarding the ODM.

So I took a look at the source code of the Connection.php class used by Doctrine-Mongo too and I haven't found the beginTransaction, commitand rollback methods that the dbal version uses.

I was clueless, then I asked myself "Is it even possible to rollback in MongoDB?", and the answer if found in the MongoDB FAQ was:

MongoDB does not use traditional locking or complex transactions with rollback

:( So I guess that's why there is no beginTransaction or whatsoever in the ODM...

But my problem remains: how can I implement a sort of rollback for my tests?

The only idea I got right now is to manually get all the ids of the Document I load and then remove them in the tearDown(). But, well... it kinda sucks, doesn't it?

Other ideas??

EDIT: After my first comment to this question, regarding the fact that I want to have the same DB in test and development, I thought: why don't use a separate test database, where the development database gets copied when the tests start, and that can be light-heartedly dropped?

Could it be a better idea? It actually looks easier and more secure to me. What do you guys think?

Thanks :)

like image 977
mokagio Avatar asked Apr 04 '12 14:04

mokagio


2 Answers

I am not using two separate DBs for development and testing

That's the first thing to address - because without a testing db, running tests will affect your development db and vice versa which is a terrible idea. You should be able to run tests in your production environment with absolute confidence that nothing you do in a test will affect your deployed site.

Setup a test connection

So, modify your parameters.yml to have something like this:

database.host: localhost
database.port: 27017
database.db:   myappname

database.test.host: localhost
database.test.port: 27017
database.test.db:   myappname-test

In addition, in your app/config/config_test.yml file override the default connnection so that anything you trigger as part of a test which requests the default document manager will receive a manager pointing at your test db:

doctrine_mongodb:
    document_managers:
        default:
            database: %database.test.db%

Prepare for tests with fixtures

Then, what you want to do effectively is:

  • truncate relevant collections
  • load fixtures

on your test db before each test.

Here's an example abstract test class:

<?php

use Doctrine\Common\DataFixtures\Executor\MongoDBExecutor as Executor,
    Doctrine\Common\DataFixtures\Purger\MongoDBPurger as Purger,
    Doctrine\Common\DataFixtures\Loader,
    Doctrine\Common\DataFixtures\ReferenceRepository,
    Symfony\Bundle\FrameworkBundle\Test\WebTestCase,
    Symfony\Bundle\FrameworkBundle\Console\Application;

abstract class AbstractTest extends WebTestCase
{
    /**
     * Array of fixtures to load.
     */
    protected $fixtures = array();

    /**
     * Setup test environment
     */
    public function setUp()
    {
        $kernel = static::createKernel(array('environment' => 'test', 'debug' => false));
        $kernel->boot();
        $this->container = $kernel->getContainer();
        $this->dm = $this->container->get('doctrine.odm.mongodb.document_manager');

        if ($this->fixtures) {
            $this->loadFixtures($this->fixtures, false);
        }
    }

    /**
     * Load fixtures
     *
     * @param array   $fixtures names of _fixtures to load
     * @param boolean $append   append data, or replace?
     */
    protected function loadFixtures($fixtures = array(), $append = true)
    {
        $defaultFixtures = false;

        $loader = new Loader();
        $refRepo = new ReferenceRepository($this->dm);

        foreach ((array) $fixtures as $name) {
            $fixture = new $name();
            $fixture->setReferenceRepository($refRepo);
            $loader->addFixture($fixture);
        }

        $purger = new Purger();
        $executor = new Executor($this->dm, $purger);
        $executor->execute($loader->getFixtures(), $append);
    }
}

Use fixtures in your tests

With the previous abstract test class, you can then write tests which use your fixture data - or not - as appropriate. Below is a trivial example.

<?php

use Your\AbstractTest,
    Your\Document\Foo;

class RandomTest extends AbstractTest
{
    /**
     * fixtures to load before each test
     */
    protected $fixtures = array(
        'APP\FooBundle\DataFixtures\MongoDB\TestFoos',
        'APP\FooBundle\DataFixtures\MongoDB\TestBars'
    );

    ...

    /**
     * Check it gets an ID (insert succeeded)
     * 
     */
    public function testCreateDefaults()
    {
        $foo = new Foo();
        $this->dm->persist($foo);
        $this->dm->flush();

        $this->assertNotNull($foo->getId());
        $this->assertSame('default value', $foo->getSomeProperty());
        // etc.
    }

    /**
     * Check result of something with a given input
     * 
     */
    public function testSomething()
    {
        $foo = $this->dm->getRepository(APPFooBundle:Foo)->findByName('Some fixture object');

        $foo->doSomething();
        $this->assertSame('modified value', $foo->getSomeProperty());
        // etc.
    }

Before each test, the fixtures you've defined will be loaded (truncating the collections they affect), giving a consistent db state on which to base your tests.

like image 195
AD7six Avatar answered Oct 08 '22 23:10

AD7six


Just drop your MongoDB database before each test and then load the fixtures you need. This way each test will be fully isolated.

like image 35
Elnur Abdurrakhimov Avatar answered Oct 08 '22 23:10

Elnur Abdurrakhimov