One of the last project I've worked on use MongoDB for collecting events, manipulate them and computing statistics. MongoDB is quite popular nowadays but I couldn't find any doc about integrating it with Python unittest.
When I run tests I like to have a self-configured environment that just works and doesn't take too long to finish. In previous projects, whenever I've used a DB I could setup a SQLite driver that stores the temporary information in memory. That makes tests fast and super easy to run as you don't have to ensure you have a running db server properly configured. Django actually has a nice approach for db testing: whenever the DB backend is SQLite then by default it uses an in-memory database.
Browsing the web I've found out Ming, the MongoDB orm, provides a "mim" module that stands for Mongo-In-Memory. I guess this module is just used internally for Ming unittests and that's why it is not that sponsored on the doc but I wanted to give it a chance for my purposes. I've created my own TestCase that creates an instance of the mim Connection to execute my tests on it. Everything looked promising for my first tentatives then I had to write some tests for the routines that were using Map Reduce and MIM showed some limits, for instance it is not possible to write the result of the MR in another database and I've got some weird errors with more complex objects. I've written some workaround and started investigating on the bugs then I've given up, considering a simpler alternative that I've first delegated as my plan B.
Plan B consisted in starting a MongoDB singleton instance from my TestCase class itself. This is more or less the code I've used, hopefully will be helpful for others:
import time
import atexit
import shutil
import tempfile
import unittest
import subprocess
import pymongo
from util import load_fixture
class MongoTemporaryInstance(object):
"""Singleton to manage a temporary MongoDB instance
Use this for testing purpose only. The instance is automatically destroyed
at the end of the program.
"""
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
atexit.register(cls._instance.shutdown)
return cls._instance
def __init__(self):
self._tmpdir = tempfile.mkdtemp()
self._process = subprocess.Popen(['mongod', '--bind_ip', 'localhost',
'--port', str(MONGODB_TEST_PORT),
'--dbpath', self._tmpdir,
'--nojournal', '--nohttpinterface',
'--noauth', '--smallfiles',
'--syncdelay', '0',
'--maxConns', '10',
'--nssize', '1', ],
stdout=open(os.devnull, 'wb'),
stderr=subprocess.STDOUT)
# XXX: wait for the instance to be ready
# Mongo is ready in a glance, we just wait to be able to open a
# Connection.
for i in range(3):
time.sleep(0.1)
try:
self._conn = pymongo.Connection('localhost', MONGODB_TEST_PORT)
except pymongo.errors.ConnectionFailure:
continue
else:
break
else:
self.shutdown()
assert False, 'Cannot connect to the mongodb test instance'
@property
def conn(self):
return self._conn
def shutdown(self):
if self._process:
self._process.terminate()
self._process.wait()
self._process = None
shutil.rmtree(self._tmpdir, ignore_errors=True)
class TestCase(unittest.TestCase):
"""TestCase with an embedded MongoDB temporary instance.
Each test runs on a temporary instance of MongoDB. Please note that
these tests are not thread-safe and different processes should set a
different value for the listening port of the MongoDB instance with the
settings `MONGODB_TEST_PORT`.
A test can access the connection using the attribute `conn`.
"""
fixtures = []
def __init__(self, *args, **kwargs):
super(TestCase, self).__init__(*args, **kwargs)
self.db = MongoTemporaryInstance.get_instance()
self.conn = self.db.conn
def setUp(self):
super(TestCase, self).setUp()
for db_name in self.conn.database_names():
self.conn.drop_database(db_name)
for fixture in self.fixtures:
load_fixture(self.conn, fixture)
MongoTemporaryInstance is the singleton class that setups the temporary instance. I know the singleton pattern in Python could be written in a different way, I just prefer this way because it is more explicit.
At the start of each test, all the databases in the instance are dropped and all the fixtures defined in the fixtures array of the subclasses of TestCase are loaded. load_fixture is a function defined elsewhere, it just loads a json file and puts the data contained in the db. At the end of the execution of the program the MongoDB temporary instance is killed and its data is destroyed.