Sunday, July 29, 2012

Unit/Integration testing with Maven and Embedded Cassandra

I've been using Cassandra on my current project. I was able to utilize embedded cassandra feature of cassandra-unit to create a testable application without the need of installing/configuring Cassandra on developer boxes.

I've created a sample application showing setup/code skeleton I used. Project is available on github here. It requires java 5 or higher (tested it with 7) and maven 3.x installed.

Required dependencies and configuration files
1. Cassandra-unit (see pom snippets below)


        
            com.netflix.astyanax
            astyanax
            ${astyanax.version}
            
             .....
            
        
        
            org.cassandraunit
            cassandra-unit
            ${cassandra.unit.version}
            test
            
             ....
            
        
      
Note: Pull project here for the full dependency list.

3. cassandra.yaml - Configuration used to start embedded version of Cassandra. Check out file in the project for configuration details. Note that all the directories required by Cassandra configured in this file are children of target directory.

4. dataset.json - Keyspace and column family definition

Test case setup and layout
Although, I'm not a big fan of using inheritance, I found that defining base class with common functionality and having tests extend it to be the easiest, cleanest approach in this case.

BaseCassandraTest.java (fragments)
   .....
    @BeforeClass
    public static void startCassandra()
            throws IOException, TTransportException,
            ConfigurationException, InterruptedException
    {
        EmbeddedCassandraServerHelper
        .startEmbeddedCassandra("cassandra.yaml");
    }

    @Before
    public void setUp() throws IOException, 
     TTransportException, ConfigurationException, InterruptedException
    {
        DataLoader dataLoader = new DataLoader("TestCluster", 
         "localhost:9272");
        dataLoader.load(new ClassPathJsonDataSet("dataset.json"));

        AstyanaxContext context = 
         new AstyanaxContext.Builder().forCluster("TestCluster")
                .forKeyspace("test_keyspace")
                .withAstyanaxConfiguration(
                 new AstyanaxConfigurationImpl()
                 .setDiscoveryType(NodeDiscoveryType.NONE))
                .withConnectionPoolConfiguration(
                        new ConnectionPoolConfigurationImpl(
                        "testConnectionPool").setPort(9272)
                                .setMaxConnsPerHost(1)
                                .setSeeds("localhost:9272"))
                .withConnectionPoolMonitor(
                 new CountingConnectionPoolMonitor())
                .buildKeyspace(ThriftFamilyFactory.getInstance());

        context.start();
        keyspace = context.getEntity();
        cassandraAccessor = 
         new CassandraAccessor(new ObjectMapper(), keyspace);
    }

    @After
    public void clearCassandra()
    {
        EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
    }

    @AfterClass
    public static void stopCassandra()
    {
        EmbeddedCassandraServerHelper.stopEmbeddedCassandra();
    }
   ...
Class listed above defines lifecycle for each Cassandra test. Embedded Cassandra is started/stoped before, after class, and keyspace is tore down, cleaned and recreated for each test.

This setup has worked pretty well. Alternate approach would be to have a test cluster setup and just configure client to connect to the cluster during tests.