1. Overview
Apache Cassandra is a powerful open-source NoSQL distributed database. In a previous tutorial, we looked at some of the basics of how to work with Cassandra and Java.
In this tutorial, we’ll build on the previous one and learn how to write reliable, self-contained unit tests using CassandraUnit.
First, we’ll start by looking at how to get the latest version of CassandraUnit set up and configured. Then we’ll explore several examples of how we can write unit tests that don’t rely on an external database server running.
And, if you’re running Cassandra in production, you can definitely cut out the complexity of running and maintaining your own server and use the Astra database instead, which is a cloud-based database built on Apache Cassandra.
2. Dependencies
Of course, we’ll need to add the standard Datastax Java driver for Apache Cassandra to our pom.xml:
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-core</artifactId>
<version>4.13.0</version>
</dependency>
In order to test our code with an embedded database server, we should also add the cassandra-unit dependency to our pom.xml as well:
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId>
<version>4.3.1.0</version>
<scope>test</scope>
</dependency>
Now that we have all the necessary dependencies configured, we can start writing our unit tests.
3. Getting Started
Throughout this tutorial, the focus of our tests will be a simple person table that we control via a simple CQL script:
CREATE TABLE person(
id varchar,
name varchar,
PRIMARY KEY(id));
INSERT INTO person(id, name) values('1234','Eugen');
INSERT INTO person(id, name) values('5678','Michael');
As we’re going to see, CassandraUnit offers several variations to help us write tests, but at the heart of them are several straightforward concepts that we’ll keep repeating:
- First, we’ll start an embedded Cassandra Server which runs in-memory within our JVM.
- Then we’ll load our person dataset into the running embedded instance.
- Finally, we’ll launch a simple query to verify that our data has been loaded correctly.
To conclude this section, a quick word on testing. In general, when writing clean unit or integration tests, we shouldn’t depend on external services that we might not be able to control or might suddenly stop working. This could have adverse effects on our test results.
Similarly, if we’re dependent on an external service, in this case a running Cassandra database, we likely won’t be able to set it up, control it and tear it down in the way we want from our tests.
4. Testing Using a Native Approach
Let’s start by looking at how to use the native API that comes with CassandraUnit. First, we’ll go ahead and define our unit test and test setup:
public class NativeEmbeddedCassandraUnitTest {
private CqlSession session;
@Before
public void setUp() throws Exception {
EmbeddedCassandraServerHelper.startEmbeddedCassandra();
session = EmbeddedCassandraServerHelper.getSession();
new CQLDataLoader(session).load(new ClassPathCQLDataSet("people.cql", "people"));
}
}
Let’s walk through the key parts of our test setup. First, we begin by starting an embedded Cassandra server. For this, all we have to do is call the startEmbeddedCassandra() method.
This will start our database server using the fixed port 9142:
11:13:36.754 [pool-2-thread-1] INFO o.apache.cassandra.transport.Server
- Starting listening for CQL clients on localhost/127.0.0.1:9142 (unencrypted)...
If we prefer to use a randomly available port, we can use the provided Cassandra YAML configuration file:
EmbeddedCassandraServerHelper
.startEmbeddedCassandra(EmbeddedCassandraServerHelper.CASSANDRA_RNDPORT_YML_FILE);
Likewise, we can also pass our own YAML configuration file when starting the server. Of course, this file needs to be in our classpath.
Next, we can go ahead and load our people.cql dataset into our database. For this, we use the ClassPathCQLDataSet class, which takes the dataset location and an optional keyspace name.
Now that we have loaded some data, and our embedded server is up and running, we can go ahead and write a simple unit test:
@Test
public void givenEmbeddedCassandraInstance_whenStarted_thenQuerySuccess() throws Exception {
ResultSet result = session.execute("select * from person WHERE id=1234");
assertThat(result.iterator().next().getString("name"), is("Eugen"));
}
As we can see, executing a simple query confirms that our test is working correctly. Awesome! We now have a way to write self-contained, independent unit tests using an in-memory Cassandra database.
Finally, when we tear down our test, we’ll clean our embedded instance:
@After
public void tearDown() throws Exception {
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
}
Executing this will drop all existing keyspaces except for the system keyspace.
5. Testing Using the CassandraUnit Abstract JUnit Test Case
To help simplify the example we saw in the last section, CassandraUnit provides an abstract test case class AbstractCassandraUnit4CQLTestCase, which takes care of the set up and tear down we saw before:
public class AbstractTestCaseWithEmbeddedCassandraUnitTest
extends AbstractCassandraUnit4CQLTestCase {
@Override
public CQLDataSet getDataSet() {
return new ClassPathCQLDataSet("people.cql", "people");
}
@Test
public void givenEmbeddedCassandraInstance_whenStarted_thenQuerySuccess()
throws Exception {
ResultSet result = this.getSession().execute("select * from person WHERE id=1234");
assertThat(result.iterator().next().getString("name"), is("Eugen"));
}
}
This time around, by extending the AbstractCassandraUnit4CQLTestCase class, all we need to do is override the getDataSet() method, which returns the CQLDataSet we wish to load.
Another subtle difference is that from our test, we need to call getSession() to gain access to the Cassandra Java driver.
6. Testing Using the CassandraCQLUnit JUnit Rule
If we don’t want to force our tests to extend AbstractCassandraUnit4CQLTestCase, then luckily CassandraUnit also provides a standard JUnit Rule:
public class JUnitRuleWithEmbeddedCassandraUnitTest {
@Rule
public CassandraCQLUnit cassandra = new CassandraCQLUnit(new ClassPathCQLDataSet("people.cql", "people"));
@Test
public void givenEmbeddedCassandraInstance_whenStarted_thenQuerySuccess() throws Exception {
ResultSet result = cassandra.session.execute("select * from person WHERE id=5678");
assertThat(result.iterator().next().getString("name"), is("Michael"));
}
}
All we have to do is declare a CassandraCQLUnit field in our test, which is a standard JUnit @Rule. This rule will prepare and manage the life cycle of our Cassandra Server.
7. Working With Spring
Typically we might integrate Cassandra with Spring in our projects. Fortunately, CassandraUnit also provides support for working with the Spring TestContext Framework.
To take advantage of this support, we need to add the cassandra-unit-spring Maven dependency to our project:
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit-spring</artifactId>
<version>4.3.1.0</version>
<scope>test</scope>
</dependency>
Now we have access to a number of annotations and classes that we can use from our tests. Let’s go ahead and write a test that uses the most basic of Spring configurations:
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ CassandraUnitTestExecutionListener.class })
@CassandraDataSet(value = "people.cql", keyspace = "people")
@EmbeddedCassandra
public class SpringWithEmbeddedCassandraUnitTest {
@Test
public void givenEmbeddedCassandraInstance_whenStarted_thenQuerySuccess() throws Exception {
CqlSession session = EmbeddedCassandraServerHelper.getSession();
ResultSet result = session.execute("select * from person WHERE id=1234");
assertThat(result.iterator().next().getString("name"), is("Eugen"));
}
}
Let’s walk through the key parts of our test. First, we start by decorating our test class with two pretty standard Spring related annotations:
- The @RunWith(SpringJUnit4ClassRunner.class) annotation will ensure that our test embeds Spring’s TestContextManager into our test, giving us access to the Spring ApplicationContext.
- We also specify a custom TestExecutionListener, called CassandraUnitTestExecutionListener, which is responsible for starting and stopping our server and finding other CassandraUnit annotations.
Here comes the crucial part; we use the @EmbeddedCassandra annotation to inject an instance of an embedded Cassandra server into our tests. Moreover, there are several properties available that we can use to further configure the embedded database server:
- configuration – a different Cassandra configuration file
- clusterName – the name of the cluster
- host – the host of our cluster
- port – the port used by our cluster
We’ve kept things simple here, opting for the default values by omitting these properties from our declaration.
For the final piece of the puzzle, we use the @CassandraDataSet annotation to load the same CQL dataset that we’ve seen previously. In the same way as before, we can send a query to verify the contents of our database are correct.
8. Conclusion
In this article, we learned several ways we can work with CassandraUnit to write stand-alone unit tests using an embedded instance of Apache Cassandra. We also discussed how we can work with Spring from our unit tests as well.
As always, the full source code of the article is available over on GitHub.