1. Overview

The main goal of the project is to make it easier to configure and run Cassandra.

Primary features are:

  • Support different versions.

  • Supports multiple platforms: Windows, Linux and OSX.

  • Provides a high-level abstraction to work with CQL scripts.

  • Provides different extensions for popular frameworks: JUnit4, JUnit5, TestNG, Spring Test.

  • Depends only on (org.apache.commons:commons-compress:1.18, org.slf4j:slf4j-api:1.7.26)

Also can be used as a part of the Spring Boot Application, see more here

1.1. Prerequisites

All that you need is add the following lines into your pom.xml and have JDK 8 or higher.

     <!-- Core -->
     <dependency>
          <groupId>com.github.nosan</groupId>
          <artifactId>embedded-cassandra</artifactId>
          <version>2.0.4</version>
    </dependency>

For testing com.github.nosan:embedded-cassandra-test module should be used.

    <!-- Test Extensions (Spring Framework, JUnit4, JUnit5, TestNG) -->
     <dependency>
         <groupId>com.github.nosan</groupId>
         <artifactId>embedded-cassandra-test</artifactId>
         <version>2.0.4</version>
         <scope>test</scope>
     </dependency>

1.2. Quick Start

The Apache Cassandra can be started using the following lines of code:

import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.List;

import com.github.nosan.embedded.cassandra.Cassandra;
import com.github.nosan.embedded.cassandra.Settings;
import com.github.nosan.embedded.cassandra.cql.CqlScript;
import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory;

class Scratch {

    public static void main(String[] args) {
        LocalCassandraFactory cassandraFactory = new LocalCassandraFactory();
        Cassandra cassandra = cassandraFactory.create();
        cassandra.start();
        try {
            Settings settings = cassandra.getSettings();
            //
        }
        finally {
            cassandra.stop();
        }
    }

}

Apache Cassandra will be started as a separate process with a default configuration.

Note, first run can take a while because Apache Casandra must be initialized. Current implementation downloads the archive from one of the following URLs just once and then initialize Apache Cassandra with it.

1.3. Configuration

The configuration can be overridden via LocalCassandraFactory. LocalCassandraFactory contains a lot of different properties for configuring Apache Cassandra, please find additional information in the following tables.

Table 1. Local Cassandra Factory
Name Value Description

allow-root

true

Allow running Cassandra under 'root' user.

version

3.11.4

Cassandra Version.

working-directory

${tmp.dir}/embedded-cassandra/${version}/apache-cassandra-${UUID}

Cassandra directory.

working-directory-customizers

Customizers that should be applied during working directory initialization.

artifact-factory

RemoteArtifactFactory

Factory that creates an Artifact.

artifact-directory

${tmp.dir}/embedded-cassandra/${version}/apache-cassandra-${version}

Directory where an artifact will be extracted.

configuration-file

Cassandra configuration file.

delete-working-directory

true

Deletes the working directory after the successful stop.

java-home

Java home directory.

jmx-local-port

JMX port to listen on.

jvm-options

JVM options that should be associated with Cassandra.

environment-variables

Environment variables that should be associated with Cassandra.

logging-file

Cassandra logging file.

startup-timeout

1m

Cassandra startup timeout.

daemon

true

Marks threads as daemons

port

The native transport port to listen for the clients on.

rack-file

Configuration file to determine which data centers and racks nodes belong to.

register-shutdown-hook

true

Register a shutdown hook with the JVM runtime.

rpc-port

Thrift port for client connections.

ssl-storage-port

The SSL port for inter-node communication.

storage-port

The port for inter-node communication.

topology-file

Configuration file for data centers and rack names and to determine network topology so that requests are routed efficiently and allow the database to distribute replicas evenly.

Table 2. Remote Artifact Factory
Name Value Description

connect-timeout

30s

Connection timeout to be used when opening a communications link to the resource referenced by URLConnection.

proxy.host

The host of the proxy to use.

proxy.port

The port of the proxy to use.

proxy.type

The proxy type to use.

read-timeout

30s

Read timeout specifies the timeout when reading from InputStream when a connection is established to a resource.

url-factory

DefaultUrlFactory

Factory that creates URLs for downloading an archive.

It is possible to run Cassandra on the random ports, just use 0 for port values.

org.yaml:snakeyaml is required to override port values with the random ports in the cassandra.yaml file.

2. Testing

There are several classes and annotations to help writing integration tests against Apache Cassandra.

2.1. Test Cassandra

TestCassandra the main class which helps us to write integration tests against Apache Cassandra. TestCassandra does not launch Apache Cassandra itself, it simply delegates calls to the underlying Cassandra and initialize it with CQL scripts. CQL scripts will be invoked by com.datastax.driver.core.Session or com.datastax.oss.driver.api.core.CqlSession after the successful start of Cassandra.

TestCassandra can be constructed with the following attributes:

  • scripts - CQL scripts to execute after the start of Cassandra

  • cassandraFactory - factory that creates Cassandra

  • connectionFactory - factory that creates Connection

TestCassandra does not depend on any Test Frameworks, so it can be used with any of them. Embedded Cassandra project offers extensions for Spring Test, JUnit4, JUnit5 and TestNG.

Here is a quick example how to start TestCassandra:

import com.datastax.driver.core.Cluster;
import com.datastax.oss.driver.api.core.CqlSession;

import com.github.nosan.embedded.cassandra.cql.CqlScript;
import com.github.nosan.embedded.cassandra.test.Connection;
import com.github.nosan.embedded.cassandra.test.TestCassandra;

class Scratch {

    public static void main(String[] args) {
        TestCassandra cassandra = new TestCassandra(CqlScript.classpath("schema.cql"));
        cassandra.start();
        try {
            Connection connection = cassandra.getConnection();
            //if com.datastax.oss:java-driver-core:4.0.1 is present
            CqlSession session = (CqlSession) connection.get();
            //if com.datastax.cassandra:cassandra-driver-core:3.7.1 is present
            Cluster cluster = (Cluster) connection.get();
        }
        finally {
            cassandra.stop();
        }
    }

}

By default com.github.nosan.embedded.cassandra.test.DefaultConnectionFactory and com.github.nosan.embedded.cassandra.local.LocalCassandraFactory are used.

2.2. TestNG

To run tests using TestNG, CassandraTestNG has to be extended.

import com.datastax.oss.driver.api.core.CqlSession;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.github.nosan.embedded.cassandra.cql.ClassPathCqlScript;
import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory;
import com.github.nosan.embedded.cassandra.test.CqlSessionFactory;
import com.github.nosan.embedded.cassandra.test.testng.CassandraTestNG;

import static org.assertj.core.api.Assertions.assertThat;

public class CassandraTestNGTests extends CassandraTestNG {

    private CqlSession session;

    /**
     * Configured Cassandra Factory.
     */
    public CassandraTestNGTests() {
        super(() -> {
            LocalCassandraFactory cassandraFactory = new LocalCassandraFactory();
            cassandraFactory.setVersion("3.11.1");
            return cassandraFactory.create();
        }, new CqlSessionConnectionFactory(), new ClassPathCqlScript("schema.cql"));
    }

    /**
     * Default Cassandra Factory.
     */
    public CassandraTestNGTests() {
        super(new ClassPathCqlScript("schema.cql"));
    }

    @BeforeClass
    public void initSession() {
        //com.github.nosan.embedded.cassandra.test.ClusterFactory.create(...)
        this.session = new CqlSessionFactory().create(getSettings());
    }

    @AfterClass
    public void closeSession() {
        if (this.session != null) {
            this.session.close();
        }
    }

    @Test
    public void findAll() {
        assertThat(this.session.execute("SELECT * FROM  test.roles")).hasSize(2);
    }

}

CassandraTestNG extends TestCassandra and contains the same constructors and methods.

2.3. JUnit4

To run tests using JUnit4, CassandraRule has to be used.

import com.datastax.driver.core.Cluster;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;

import com.github.nosan.embedded.cassandra.cql.CqlScript;
import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory;
import com.github.nosan.embedded.cassandra.test.ClusterFactory;
import com.github.nosan.embedded.cassandra.test.junit4.CassandraRule;

import static org.assertj.core.api.Assertions.assertThat;

public class CassandraRuleTests {

    /**
     * Default Cassandra Factory.
     */
    @ClassRule
    public static final CassandraRule cassandra = new CassandraRule(CqlScript.classpath("schema.cql"));

    /**
     * Configured Cassandra Factory.
     */
    @ClassRule
    public static final CassandraRule cassandra = new CassandraRule(() -> {
        LocalCassandraFactory factory = new LocalCassandraFactory();
        factory.setVersion("3.11.3");
        return factory.create();
    }, new ClusterConnectionFactory(), CqlScript.classpath("schema.cql"));

    private static Cluster cluster;

    @BeforeClass
    public static void initCluster() {
        cluster = new ClusterFactory().create(cassandra.getSettings());
        //or cluster = cassandra.getNativeConnection(Cluster.class);
    }

    @AfterClass
    public static void closeCluster() {
        if (cluster != null) {
            cluster.close();
        }
    }

    @Test
    public void findAll() {
        assertThat(cluster.connect().execute("SELECT * FROM  test.roles")).hasSize(2);
    }

}

CassandraRule extends TestCassandra and contains the same constructors and methods.

2.4. JUnit5

To run tests using JUnit5, CassandraExtension has to be used.

import com.datastax.oss.driver.api.core.CqlSession;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.github.nosan.embedded.cassandra.cql.CqlScript;
import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory;
import com.github.nosan.embedded.cassandra.test.CqlSessionFactory;
import com.github.nosan.embedded.cassandra.test.junit5.CassandraExtension;

import static org.assertj.core.api.Assertions.assertThat;

class CassandraExtensionTests {

    /**
     * Default Cassandra Factory.
     */
    @RegisterExtension
    public static final CassandraExtension cassandra = new CassandraExtension(CqlScript.classpath("schema.cql"));

    /**
     * Configured Cassandra Factory.
     */
    @RegisterExtension
    public static final CassandraExtension cassandra = new CassandraExtension(() -> {
        LocalCassandraFactory factory = new LocalCassandraFactory();
        factory.setVersion("3.11.3");
        return factory.create();
    }, new CqlSessionConnectionFactory(), CqlScript.classpath("schema.cql"));

    private static CqlSession session;

    @BeforeAll
    static void initSession() {
        session = new CqlSessionFactory().create(cassandra.getSettings());
        //or session = cassandra.getNativeConnection(CqlSession.class);

    }

    @AfterAll
    static void closeSession() {
        if (session != null) {
            session.close();
        }
    }

    @Test
    void findAll() {
        assertThat(session.execute("SELECT * FROM  test.roles")).hasSize(2);
    }

}

CassandraExtension extends TestCassandra and contains the same constructors and methods.

2.5. Spring

There are several annotations to help writing integration tests against Apache Cassandra.

2.5.1. @EmbeddedCassandra

For running Apache Cassandra using Spring, @EmbeddedCassandra annotation has to be used. Apache Cassandra could be initialized with CQL scripts using scripts and statements attributes.

@EmbeddedCassandra is handled by EmbeddedCassandraContextCustomizer which is enabled by default.

Here is a small example:

import com.datastax.driver.core.Cluster;
import com.datastax.oss.driver.api.core.CqlSession;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.github.nosan.embedded.cassandra.test.ClusterFactory;
import com.github.nosan.embedded.cassandra.test.CqlSessionFactory;
import com.github.nosan.embedded.cassandra.test.TestCassandra;
import com.github.nosan.embedded.cassandra.test.spring.EmbeddedCassandra;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@ContextConfiguration
@EmbeddedCassandra(scripts = "/schema.cql")
class EmbeddedCassandraTests {

    @Autowired
    private CqlSession session;

    @Test
    void findAll() {
        assertThat(this.session.execute("SELECT * FROM test.roles")).hasSize(2);
    }

    //com.datastax.oss:java-driver-core:4.0.1
    @Configuration
    static class CqlSessionConfiguration {

        @Bean
        public CqlSession cqlSession(TestCassandra testCassandra) {
            return new CqlSessionFactory().create(testCassandra.getSettings());
        }

    }

    //com.datastax.cassandra:cassandra-driver-core:3.7.1
    @Configuration
    static class ClusterConfiguration {

        @Bean
        public Cluster cassandraCluster(TestCassandra cassandra) {
            return new ClusterFactory().create(cassandra.getSettings());
        }

    }

}

@EmbeddedCassandra annotation contains the following attributes:

  • scripts - The paths to the CQL scripts to execute.

  • statements - CQL statements (will be executed after statements loaded from scripts)

  • encoding - The encoding for the supplied CQL scripts

  • port - The native transport port to listen for the clients on.

  • rpc-port - Thrift port for client connections.

  • storage-port - The port for inter-node communication.

  • jmx-local-port - JMX port to listen on.

  • ssl-storage-port - The ssl port for inter-node communication.

  • version - version to use

  • configurationFile - Cassandra configuration file

If you would like to override some properties which annotation does not have,EmbeddedCassandraFactoryCustomizer can be used. Here is a quick example:

import java.nio.file.Path;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.github.nosan.embedded.cassandra.local.LocalCassandraFactory;
import com.github.nosan.embedded.cassandra.test.spring.EmbeddedCassandra;
import com.github.nosan.embedded.cassandra.test.spring.CassandraFactoryCustomizer;

@ExtendWith(SpringExtension.class)
@ContextConfiguration
@EmbeddedCassandra
class EmbeddedCassandraCustomizerTests {

    @Test
    void testMe() {
    }

    @Configuration
    static class CassandraFactoryCustomizerConfiguration {

        @Bean
        public CassandraFactoryCustomizer<LocalCassandraFactory> allowRootCustomizer() {
            return factory -> factory.setAllowRoot(true);
        }

        @Bean
        public CassandraFactoryCustomizer<LocalCassandraFactory> java8HomeCustomizer(
                @Value("${JAVA8_HOME:}") Path javaHome) {
            return factory -> factory.setJavaHome(javaHome);
        }

    }

}
You can declare CassandraFactory, ConnectionFactory bean(s) to take control of the Cassandra instance.

2.5.2. @Cql

@Cql annotation is used to annotate a test method or a class to configure CQL scripts to be executed against a given session during integration tests. Script execution is performed by the CqlExecutionListener, which is enabled by default.

Here is a small example:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.github.nosan.embedded.cassandra.test.spring.Cql;
import com.github.nosan.embedded.cassandra.test.spring.EmbeddedCassandra;

@ExtendWith(SpringExtension.class)
@EmbeddedCassandra(scripts = "/schema.cql")
class CassandraTests {

    @Test
    @Cql(scripts = "/data.cql")
    void testMe() {
    }

}

@Cql annotation contains the following attributes:

  • scripts - The paths to the CQL scripts to execute.

  • statements - CQL statements (will be executed after statements loaded from scripts)

  • encoding - The encoding for the supplied CQL scripts

  • executionPhase - When the CQL scripts and statements should be executed

  • session - The bean name of the com.datastax.oss.driver.api.core.CqlSession, com.datastax.driver.core.Session or com.github.nosan.embedded.cassandra.test.Connection against which the scripts should be executed. (can be empty)

@Cql annotation may be used in conjunction with @EmbeddedCassandra annotation.

2.5.3. Spring Boot

There is no much difference between Spring Boot test and Spring test, hence, to run the test using Spring Boot, @EmbeddedCassandra can be used.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.github.nosan.embedded.cassandra.test.spring.EmbeddedCassandra;

@SpringBootTest
@RunWith(SpringRunner.class) // for JUnit4
@EmbeddedCassandra(scripts = "/schema.cql")
public class CassandraTests {

    @Test
    public void testMe() {
    }
}

You can also use this @DataCassandraTest annotation to test your Cassandra components.

Note, that @SpringBootTest knows nothing about @EmbeddedCassandra and if you want to run Cassandra on the random ports, you need to explain to Spring Boot that it needs to use random ports instead of defaults. One of the ways it is ClusterBuilderCustomizer.

Here is an example:

import java.net.InetAddress;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.cassandra.ClusterBuilderCustomizer;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import com.github.nosan.embedded.cassandra.Settings;
import com.github.nosan.embedded.cassandra.test.TestCassandra;
import com.github.nosan.embedded.cassandra.test.spring.EmbeddedCassandra;

@SpringBootTest
@EmbeddedCassandra(scripts = "/schema.cql", port = "0", rpcPort = "0", storagePort = "0", sslStoragePort = "0",
        jmxLocalPort = "0")
class CassandraSpringBootTests {

    @Test
    void testMe() {
    }

    @TestConfiguration
    static class EmbeddedClusterBuilderCustomizerConfiguration {

        @Bean
        ClusterBuilderCustomizer embeddedClusterCustomizer(TestCassandra cassandra) {
            return builder -> {
                Settings settings = cassandra.getSettings();
                Integer port = settings.portOrSslPort().orElse(null);
                InetAddress address = settings.address().orElse(null);
                if (port != null && address != null) {
                    builder.withPort(port).addContactPoints(address);
                }
            };
        }

    }

}