Software Development Magazine - Project Management, Programming, Software Testing |
Scrum Expert - Articles, tools, videos, news and other resources on Agile, Scrum and Kanban |
Unit Testing NoSQL Databases Applications with NoSQLUnit
Alex Soto, http://www.lordofthejars.com/
NoSQLUnit is an open source JUnit extension for writing tests of Java applications that use NoSQL databases. The goal of NoSQLUnit is to manage the lifecycle of NoSQL engines. It helps you to maintain the databases under test into known state and standardize the way we write tests for NoSQL applications. Current version supports the following engines: MongoDB, Cassandra, HBase, Redis and Neo4j.
Web Site: https://github.com/lordofthejars/nosql-unit
Version Tested: NoSQLUnit 0.7.0 on Linux Ubuntu 12.04
License & Pricing: Open Source (Apache License 2.0)
Support: https://github.com/lordofthejars/nosql-unit/issues
Overview
Software tests in general and unit tests in particular should follow the FIRST rules:
- Fast. Tests should be fast to execute. If they are fast you will run them more often.
- Isolated. The execution of one test should not condition the result of another test.
- Repeatable. Without any change, your tests should always return the same result.
- Self-validated. Tests should be treated as production code. Among other things, this implies that the tests should be written in a clean way. Anyone should understand quickly and exactly the purpose of that test.
- Timely. Test should be written before coding the production code.
There is an exception for high level tests (integration tests, acceptance tests, smoke tests, ...) where tests can be slow because of external dependencies with database servers, filesystem access or network communication.
The isolation rule is the most broken one when people write tests for the persistence layer. Let us suppose that we have a piece of code that contains two methods: one that inserts an item into database and another one that counts the number of elements in the database. These functions would have at least two unit tests, one for each method. This is the problem: the count test would return different result depending on the order of the tests execution. Clearly these tests are not running in a free environment and are not isolated. DBUnit solves this problem for relational database and NoSQLUnit does the same for NoSQL systems.
NoSQLUnit
NoSQLUnit is a JUnit extension that help you to:
- manage lifecycle of NoSQL servers.
- maintain the database under test into known state, fixing the isolation problem.
- standardize the way we write tests for NoSQL databases.
NoSQLUnit is composed by two groups of JUnit Rules and two annotations.
The first group starts and stops the database servers used within the tests. At least each supported database implements one rule of this kind, but it can contains more than one depending on start up modes provided by each backend. Almost all supported engines provide two ways to initialize the NoSQL system.
- Embedded mode: This mode takes care of starting and stopping the database engine inside the same JVM. Database could be an " in-memory " or not, this is determined by the vendor. This mode will be typically used during the unit testing phase.
- Managed mode. This mode is in charge of starting NoSQL database from its installation directory and stopping it. This will typically be used during the integration tests.
The next group contains one JUnit rule for each supported engine, and can be understood as a connection to the NoSQL database. This connection will be used for maintaining the database into a controlled state.
The first annotation (@UsingDataSet) is used for populating database with contents of defined data set files.
The second annotation (@ShouldMatchDataset) is an optional annotation, which compares content of database against an expected data set file. This annotation is useful asserting the database state directly from the test code may imply a huge amount of work.
Note that in both annotations, data set files are required and their format will vary according to NoSQL vendor.
Let's summarize the lifecycle of NoSQLUnit.
First of all, the required engines for executing the tests are started. Then the databases are cleaned and a test dataset is inserted into each database. Then the test is executed. An optional step verifies if the databases contain the expected data. Finally, NoSQLUnit stops the running engines.
Installation
NoSQLUnit is published to Maven Central Repository, so all you have to do is add the dependency, which will be different depending on the database engine, into your Maven, Gradle or Ivy dependencies file.
In the next example, we are going to write a test for an application that uses MongoDB as backend. Our pom file is:
<dependency> <groupId>com.lordofthejars</groupId> <artifactId>nosqlunit-mongodb</artifactId> <version>0.7.0</version> </dependency>
Example
This example shows how to use NoSQLUnit when you use MongoDB as a database. This simple Java class inserts a new book into a MongoDB collection.
private DBCollection booksCollection; public BookManager(DBCollection booksCollection) { this.booksCollection = booksCollection; } public void create(Book book) { DBObject dbObject = MONGO_DB_BOOK_CONVERTER.convert(book); booksCollection.insert(dbObject); }
This NoSQLUnit test code verifies that the create method inserts a new book into database:
import static com.lordofthejars.nosqlunit.mongodb.ManagedMongoDb.MongoServerRuleBuilder.newManagedMongoDbRule; import static com.lordofthejars.nosqlunit.mongodb.MongoDbRule.MongoDbRuleBuilder.newMongoDbRule; public class WhenANewBookIsCreated { @ClassRule public static ManagedMongoDb managedMongoDb = newManagedMongoDbRule().mongodPath("/opt/mongo").build(); [1] @Rule public MongoDbRule managedMongoDbRule = newMongoDbRule().defaultManagedMongoDb("test"); [2] @Test @UsingDataSet(locations="initialData.json", loadStrategy=LoadStrategyEnum.CLEAN_INSERT) [3] @ShouldMatchDataSet(location="expectedData.json") [4] public void book_should_be_inserted_into_repository() { BookManager bookManager = new BookManager(MongoDbUtil.getCollection(Book.class.getSimpleName())); Book book = new Book("The Lord Of The Rings", 1299); bookManager.create(book); [5] } }
This code contains all the typical phases of a NoSQLUnit test. We start by creating a managed MongoDB instance. [1]
Then we configure a connection to maintain MongoDB collections into a known state. [2] We define a connection to a managed MongoDB server using default parameters (localhost:27017) and using test database. This can be obviously changed by calling methods of MongoDbConfiguration class provided by newMongoDbRule() method.
After configuring the connection, it is time for setting which data is inserted in the database [3]. For this we use @UsingDataSet, which cleans the database and populate it with the content of the initialData.json file shown below.
{ "Book": [ {"title":"The Hobbit","numberOfPages":293} ] }
The first element ("Book") is the name of the collection, and then we simply must set the json documents belonging to that collection.
We also need a way to verify that the inserted data is what we expect [4]. We could establish a connection to server and assert this manually, but we can also use the @ShouldMatchDataSet annotation. And finally at [5] the code under test is called which saves a new book into MongoDB Book collection. See https://github.com/lordofthejars/nosql-unit/tree/master/nosqlunit-demo for a complete example.
Supported Databases
This table shows the supported engines:
Engine |
Embedded Rule |
Managed Rule |
File Format |
MongoDB |
InMemoryMongoDB |
ManagedMongoDB |
json |
Cassandra |
EmbeddedCassandra |
ManagedCassandra |
cassandra-unit json |
HBase |
EmbeddedHBase |
ManagedHBase |
json |
Redis |
EmbeddedRedis |
ManagedRedis |
json |
Neo4j |
EmbeddedNeo4j |
ManagedNeoServer |
graphML xml |
InMemoryNeo4j |
ManagedWrappingNeoServer |
Advanced Features
NoSQLUnit offers some additional features related to the NoSQL world:
- Tests can be run for applications deployed in the cloud. You should not define any lifecycle rule and configure the connection rule accordingly.
- NoSQL applications might use a polyglot persistence approach. Your shopping cart could for example be implemented using Redis and the reports can be stored into MongoDB. NoSQLUnit supports this approach by providing the @Selective annotation.
- Partial support to JSR-330, so underlying connection used for maintaining database into known state can be used in your test. This is very useful when an In-Memory embedded lifecycle approach is used.
- Testing replication.
Conclusions
Writing tests for persistence layer can be a really hard work. You need to create a lot of data set files and every data set file might involve the creation of a huge amount of data. Although this is not an easy task, don't let your persistence tests ruin your internal quality.
NoSQLUnit will continue its improvement by supporting more NoSQL systems and integration with Maven and Arquillian.
Although NoSQLUnit does not implement a plugin system to add support for additional engines, it is easy to add them by extending an abstract class and an interface.
Don't hesitate to ask support for any other NoSQL database or any other capability. You can open an enhancement issue at https://github.com/lordofthejars/nosql-unit/issues.
Further Readings
NoSQLUnit documentation: https://github.com/lordofthejars/nosql-unit
Cassandra site: http://cassandra.apache.org/
Cassandra-Unit site: https://github.com/jsevellec/cassandra-unit
HBase site: http://hbase.apache.org/
MongoDB site: http://www.mongodb.org/
Redis site: http://redis.io/
Neo4j site: http://neo4j.org/
NoSQL Distilled book: http://martinfowler.com/books/nosql.html
Unit Testing and Database Articles
Behavior Driven Database Development (BDDD)
More Software Testing and Database Knowledge
Click here to view the complete list of tools reviews
This article was originally published in the Winter 2012 issue of Methods & Tools
Methods & Tools Testmatick.com Software Testing Magazine The Scrum Expert |