In my previous post I described how you can setup Spring and Hibernate in combination with the MongoDB. In this post I show you how you can write your unit test for this setup including a workaround for the fact that there isn’t an embedded version of MongoDB.
Of course you can check manually if your code works by checking the content of the MongoDB. You can use the MongoDB shell or the REST interface by using ‘curl’ (when using REST make sure you start the database with the ‘–rest’ option). For example see the result of curl 'http://localhost:28017/pascalalma/logItem/'
pascal@~$ curl ‘http://localhost:28017/pascalalma/logItem/ ‘
{
“offset” : 0,
“rows”: [
{ “_id” : { “$oid” : “4e1c2ffca0eee881985e04f0” }, “_class” : “net.pascalalma.mongo.entity.LogItem”, “message” : “just some text 0” } ,
{ “_id” : { “$oid” : “4e1c2ffda0eee881985e04f1” }, “_class” : “net.pascalalma.mongo.entity.LogItem”, “message” : “just some text 1” } ,
{ “_id” : { “$oid” : “4e1c2ffda0eee881985e04f2” }, “_class” : “net.pascalalma.mongo.entity.LogItem”, “message” : “just some text 2” } ,
{ “_id” : { “$oid” : “4e1c4289a0ee3d15a8af39d3” }, “_class” : “net.pascalalma.mongo.entity.LogItem”, “message” : “just some text 0” }
],
“total_rows” : 3 ,
“query” : {} ,
“millis” : 0
}
pascal@~$
However, when building software you want your test to be performed automatically so we use JUnit to perform the tests and checks. And I use JUnit to start and stop my local MongoDB instance. Here is how it works (I assume you have the same project setup as I created in my previous post):
- Wire the Spring beans in your test context
I created a SpringContext class for testing purposes:
package net.pascalalma.mongo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Only to be used in test classes!! * * @author pascal */ public class MyTestApplicationContext { private static MyTestApplicationContext testContext = null; ApplicationContext context = null; private MyTestApplicationContext() { } public static MyTestApplicationContext getInstance() { if (testContext == null) { testContext = new MyTestApplicationContext(); testContext.initialise(); } return testContext; } public void initialise() { context = new ClassPathXmlApplicationContext( new String[]{"spring-config.xml"}); } public Object getBean(String name) { return context.getBean(name); } public ApplicationContext getSpringContext() { return context; } }
With this class I can access my Spring beans easily in my Unit tests.
I use ProcessBuilder to start and stop the local MongoDB instance before the test class is executed. The base Testclass looks like this:
package net.pascalalma.mongo; import org.apache.log4j.Logger; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; public class MongoDBTest { private static final Logger logger = Logger.getLogger(MongoDBTest.class); static Process p = null; @Before public void setUp() throws Exception { //@Todo Run mongo with a test specific .js file to produce initial data state } @After public void tearDown() throws Exception { //@Todo Drop database } @BeforeClass public static void beforeClass() throws Exception { String[] command = new String[]{"/Users/pascal/development/mongodb-osx-x86_64-1.8.2/bin/mongod", "--dbpath", "/Users/pascal/development/mongodb/data", "--rest"}; ProcessBuilder pb = new ProcessBuilder(command); p = pb.start(); logger.debug("Process started with pid: " + p); } @AfterClass public static void afterClass() throws Exception { // Stop mongod process boolean processClosed = false; Thread.sleep(500); if (p != null) { while (!processClosed) { try { p.destroy(); processClosed = true; Thread.sleep(500); logger.info(" Process destroyed: " + p.exitValue()); } catch (IllegalThreadStateException itse) { logger.warn(itse); processClosed = false; } } } } }
And the actual test class:
package net.pascalalma.mongo; import junit.framework.Assert; import net.pascalalma.mongo.entity.LogItem; import net.pascalalma.mongo.services.LogService; import org.apache.log4j.Logger; import org.bson.types.ObjectId; import org.junit.Before; import org.junit.Test; public class LogServiceTest extends MongoDBTest { private static final Logger logger = Logger.getLogger(LogServiceTest.class); private LogService service = null; @Test public void testCreateAndFindLog() throws Exception { ObjectId id = null; for (int i = 0; i < 3; i++) { LogItem log = new LogItem(); log.setMessage("just some text " + i); id = service.add(log); logger.debug("log.id = " + id); } LogItem logFound = service.get(id); logger.debug("log = " + logFound.toString()); Assert.assertNotNull(logFound); } @Before public void setUp() throws Exception { logger.info("setting up test"); super.setUp(); service = (LogService) MyTestApplicationContext.getInstance().getBean("logService"); //@TODO Run mongo with a test specific .js file to produce initial data state } }
The test output shows it ran successfully:
——————————————————-
T E S T S
——————————————————-
Running net.pascalalma.mongo.LogServiceTest
DEBUG [main] net.pascalalma.mongo.MongoDBTest: Process started with pid: java.lang.UNIXProcess@603b1d04
INFO [main] net.pascalalma.mongo.LogServiceTest: setting up test
DEBUG [main] net.pascalalma.mongo.services.MongoDBLoggerServiceImpl: Adding a new LogItem instance
DEBUG [main] net.pascalalma.mongo.LogServiceTest: log.id = 4e2fc241a0eea3de77684fdf
DEBUG [main] net.pascalalma.mongo.services.MongoDBLoggerServiceImpl: Adding a new LogItem instance
DEBUG [main] net.pascalalma.mongo.LogServiceTest: log.id = 4e2fc241a0eea3de77684fe0
DEBUG [main] net.pascalalma.mongo.services.MongoDBLoggerServiceImpl: Adding a new LogItem instance
DEBUG [main] net.pascalalma.mongo.LogServiceTest: log.id = 4e2fc241a0eea3de77684fe1
DEBUG [main] net.pascalalma.mongo.LogServiceTest: log = LogItem [id=4e2fc241a0eea3de77684fe1, message=just some text 2, timestamp=null]
INFO [main] net.pascalalma.mongo.MongoDBTest: Process destroyed: 12
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.798 secResults :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
There is definitely room for improvement, like adding the possibility to run ‘.js’ scripts to set the database in a predefined state in the @Before method of the test class and remove the database in the @After method.
Another thing that bothered me is the ‘Thread.sleep’ that I had to add at some places to make it work. Nevertheless I showed how to setup a basic test case with MongoDB/Spring combination and if you have a better solution please let me know!
There are situations where installing the software is simply denied/unfavorable, normally happens in case of CI (Continuous integration) testing with Bamboo or Hudson. Where the machine running the server has no MongoDB installed. It is likely that all the persistence and even business tests fail badly. I am not able to find the solution until now, however, do you have any idea to unit test the application in such circumstances.
Thanks for sharing your experiences in advance!