How to Use Testcontainers With ScyllaDB
Learn how to use Testcontainers to create lightweight, throwaway instances of ScyllaDB for testing with hands-on example.
Join the DZone community and get the full member experience.
Join For FreeSetting up a full database environment for every round of integration testing can be time-consuming and complex. This post walks through how to use the Testcontainers library to spin up lightweight, throwaway ScyllaDB instances for testing purposes. We’ll explore a practical example that covers initializing the database and running tests against it.
Testcontainers: A Tool for Integration Testing
You automatically unit test your code and (hopefully) integration test your system…but what about your database? To ensure that the application works as expected, you need to extend beyond unit testing. You also need to automatically test how the units interact with one another and how they interact with external services and systems (message brokers, data stores, and so on). But running those integration tests requires the infrastructure to be configured correctly, with all the components set to the proper state. You also need to confirm that the tests are isolated and don’t produce any side effects or “test pollution.” How do you reduce the pain…and get it all running in your CI/CD process?
This is where Testcontainers come into play. It is an open source library for throwaway, lightweight instances of databases, message brokers, web browsers, or just about anything that can run in a Docker container. You define your dependencies in code, which makes it well-suited for CI/CD processes.
When you run your tests, a ScyllaDB container will be created and then deleted. This allows you to test your application against a real instance of the database without having to worry about complex environment configurations. It also ensures that the database setup has no effect on the production environment.
Using Testcontainers with ScyllaDB offers several practical benefits::
- It launches Dockerized databases on demand, so you get a fresh environment for every test run.
- It isolates tests with throwaway containers. There’s no test interference or state leakage since each test gets a pristine database state
- Tests are fast and realistic, since the container starts in seconds, ScyllaDB responds fast, and actual CQL responses are used.
Tutorial: Building a ScyllaDB Test Step-by-Step
The Testcontainers ScyllaDB integration works with Java, Go, Python (see example here), and Node.js. Here, we’ll walk through an example of how to use it with Java. The steps described below are applicable to any programming language and its corresponding testing framework. In our specific Java example, we will be using the JUnit 5 testing framework.
The integration between Testcontainers and ScyllaDB uses Docker.
Step 1: Configure Your Project Dependencies
Before we begin, make sure you have:
- Java 21 or newer installed
- Docker installed and running (required for Testcontainers)
- Gradle 8
Note: If you are more comfortable with Maven, you can still follow this tutorial, but the setup and test execution steps will be different.
To verify that Java 21 or newer is installed, run:
java --version
To verify that Docker is installed and running correctly, run:
docker run hello-world
To verify that Gradle 8 or newer is installed, run:
gradle --version
Once you have verified that all of the relevant project dependencies are installed and ready, you can move on to creating a new project.
mkdir testcontainers-scylladb-java
cd testcontainers-scylladb-java
gradle init
A series of prompts will appear. Here are the relevant choices you need to select:
- Select
application
- Select
java
- Enter Java version:
21
- Enter project name:
testcontainers-scylladb-java
- Select application structure:
Single application project
- Select build script DSL:
Groovy
- Select test framework:
JUnit Jupiter
- For "Generate build using new APIs and behavior" select
no
After that part is finished, to verify the successful initialization of the new project, run:
./gradlew --version
If everything goes well, you should see a build.gradle
file in the app
folder. You will need to add the following dependencies in your app/build.gradle
file:
dependencies {
// Use JUnit Jupiter for testing.
testImplementation libs.junit.jupiter
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// This dependency is used by the application.
implementation libs.guava
// Add the required dependencies for the test
testImplementation 'org.testcontainers:scylladb:1.20.5'
testImplementation 'com.scylladb:java-driver-core:4.18.1.0'
implementation 'ch.qos.logback:logback-classic:1.4.11'
}
Also, to get test report output in the terminal, you will need to add testLogging
to app/build.gradle
file as well:
tasks.named('test') { // Use JUnit Platform for unit tests.
useJUnitPlatform()
// Add this testLogging configuration to get
// the test results in terminal
testLogging {
events "passed", "skipped", "failed"
showStandardStreams = true
exceptionFormat = 'full'
showCauses = true
}
}
Once you're finished editing the app/build.gradle file, you need to install the dependencies by running this command in the terminal:
./gradlew build
You should see the BUILD SUCCESSFUL
output in the terminal. The final preparation step is to create a ScyllaDBExampleTest.java
file somewhere in the src/test/java
folder. JUnit will find all tests in the src/test/java
folder. For example:
touch src/test/java/org/example/ScyllaDBExampleTest.java
Step 2: Launch ScyllaDB in a Container
Once the dependencies are installed and the ScyllaDBExampleTest.java
file created, you can copy and paste the code provided below to the ScyllaDBExampleTest.java
file.
This code will start a fresh ScyllaDB instance for every test in this file in the setUp
method. To ensure the instance will get shut down after every test, we've created the tearDown
method, too.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.scylladb.ScyllaDBContainer;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import java.net.InetSocketAddress;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class ScyllaDBExampleTest {
private ScyllaDBContainer scylladb;
private CqlSession session;
@BeforeEach
public void setUp() {
scylladb = new ScyllaDBContainer("scylladb/scylla:2025.1")
.withExposedPorts(9042, 19042);
scylladb.start();
}
@AfterEach
public void tearDown() {
if (session != null) {
session.close();
}
if (scylladb != null) {
scylladb.stop();
}
}
}
Step 3: Connect via the Java Driver
You’ll connect to the ScyllaDB container by creating a new session.
To do so, you’ll need to update your setUp
method in the ScyllaDBExampleTest.java
file:
@BeforeEach
public void setUp() {
scylladb = new ScyllaDBContainer("scylladb/scylla:2025.1")
.withExposedPorts(9042, 19042);
scylladb.start();
// Add the following code to create a connection to ScyllaDB:
session = CqlSession.builder()
.addContactPoint(new InetSocketAddress(scylladb.getHost(), scylladb.getMappedPort(9042)))
.withLocalDatacenter("datacenter1")
.build();
}
Step 4: Define Your Schema
Now that you have the code to run ScyllaDB and connect to it, you can use the connection to create the schema for the database.
Let's define the schema by updating your setUp
method in the ScyllaDBExampleTest.java
file:
@BeforeEach
public void setUp() {
scylladb = new ScyllaDBContainer("scylladb/scylla:2025.1")
.withExposedPorts(9042, 19042);
scylladb.start();
session = CqlSession.builder()
.addContactPoint(new InetSocketAddress(scylladb.getHost(), scylladb.getMappedPort(9042)))
.withLocalDatacenter("datacenter1")
.build();
// Add the following code to create an example schema
session.execute("CREATE KEYSPACE IF NOT EXISTS test_keyspace WITH replication = "
+ "{'class': 'NetworkTopologyStrategy', 'datacenter1': 1}");
session.execute("USE test_keyspace");
session.execute("CREATE TABLE IF NOT EXISTS users (id UUID PRIMARY KEY, name text, age int)");
}
Step 5: Insert and Query Data
Once you have prepared the ScyllaDB instance, you can run operations on it.
To do so, let's add a new method to our ScyllaDBExampleTes
t class in the ScyllaDBExampleTest.java
file:
@Test
public void testScyllaDBOperations() {
// Insert sample data
UUID user1Id = UUID.randomUUID();
UUID user2Id = UUID.randomUUID();
// Add two new users
session.execute("INSERT INTO users (id, name, age) VALUES (?, ?, ?)", user1Id, "John Doe", 30);
session.execute("INSERT INTO users (id, name, age) VALUES (?, ?, ?)", user2Id, "Jane Doe", 27);
// Retrieve and verify the inserted data
ResultSet results = session.execute("SELECT * FROM users");
int count = 0;
for (Row row : results) {
assertNotNull(row.getString("name"));
assertNotNull(row.getInt("age"));
count++;
}
assertEquals(2, count); // Ensure two new users are present
}
Step 6: Run and Validate the Test
Your test is now complete and ready to be executed!
Use the following command to execute the test:
./gradlew clean test --no-daemon
If the execution is successful, you'll notice the container starting in the logs, and the test will pass if the assertions are met.
Here's an example of what a successful terminal output might look like:
12:05:26.708 [Test worker] DEBUG com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager -- ep-00000012: connection released [route: {}->unix://localhost:2375][total available: 1; route allocated: 1 of 2147483647; total allocated: 1 of 2147483647]
12:05:26.708 [Test worker] DEBUG org.testcontainers.utility.ResourceReaper -- Removed container and associated volume(s): scylladb/scylla:2025.1
ScyllaDBExampleTest > testScyllaDBOperations() PASSED
BUILD SUCCESSFUL in 35s 3 actionable tasks: 3 executed
Full Code Example
The repository for the full code example can be found here.
Level Up: Extending Your ScyllaDB Tests
That only covers the basics. Here are some additional uses you might want to explore on your own:
- Test schema migrations - Verify that your database evolution scripts work correctly.
- Simulate multi-node clusters - Use multiple containers to test your application with multi-node and multi-dc scenarios.
- Benchmark performance - Measure your application’s throughput under various workloads.
- Test failure scenarios - Simulate how your application handles network partitions or node failures.
// Example: Creating a multi-node cluster
Network network = Network.newNetwork();
scyllaNode1 = new ScyllaDBContainer("scylladb/scylla:2025.1")
.withExposedPorts(9042, 19042)
.withNetwork(network)
.withNetworkAliases("scylla-node1");
scyllaNode2 = new ScyllaDBContainer("scylladb/scylla:2025.1")
.withExposedPorts(9042, 19042)
.withNetwork(network)
.withNetworkAliases("scylla-node2")
.withCommand("--seeds=scylla-node1");
scyllaNode1.start();
scyllaNode2.start();
scyllaNode2.stop(); // Example: Node failure simulation
Wrap-Up: Practical ScyllaDB Testing
This approach demonstrates how to run integration tests against a real ScyllaDB instance in Java, without requiring a permanent installation. By simulating realistic database behavior during testing, it helps ensure more reliable production outcomes. You can adapt this method to suit your project’s specific requirements and explore further examples to refine your testing strategy.
Published at DZone with permission of Eduard Knezovic. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments