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
andOSX
. -
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
.
Name | Value | Description |
---|---|---|
allow-root |
|
Allow running Cassandra under 'root' user. |
version |
|
Cassandra Version. |
working-directory |
|
Cassandra directory. |
working-directory-customizers |
Customizers that should be applied during working directory initialization. |
|
artifact-factory |
|
Factory that creates an Artifact. |
artifact-directory |
|
Directory where an artifact will be extracted. |
configuration-file |
Cassandra configuration file. |
|
delete-working-directory |
|
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 |
|
Cassandra startup timeout. |
daemon |
|
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 |
|
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. |
Name | Value | Description |
---|---|---|
connect-timeout |
|
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 |
|
Read timeout specifies the timeout when reading from InputStream when a connection is established to a resource. |
url-factory |
|
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 ofCassandra
-
cassandraFactory
- factory that createsCassandra
-
connectionFactory
- factory that createsConnection
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 fromscripts
) -
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 fromscripts
) -
encoding
- The encoding for the supplied CQL scripts -
executionPhase
- When the CQL scripts and statements should be executed -
session
- The bean name of thecom.datastax.oss.driver.api.core.CqlSession
,com.datastax.driver.core.Session
orcom.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);
}
};
}
}
}