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
, commit
and 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 :)
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:
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.
Just drop your MongoDB database before each test and then load the fixtures you need. This way each test will be fully isolated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With