Testcontainers is a popular testing library that enables developers to write tests against disposable, containerized instances of databases, message brokers, and other essential services — anything that can fit in a container.
For database testing, Testcontainers integrates with Oracle Database Free, to offer a powerful, developer-focused solution to spin up real, isolated database instances directly within their tests, ensuring reliable and consistent environments. Unlike mock or in-memory databases, Testcontainers allows developers to test against actual database behavior, which helps catch issues that might only surface in real-world conditions. The use of Testcontainers in your application tests leads to more robust applications and fewer surprises during real-world deployments.
In this article, we’ll cover database-centric examples of using Testcontainers from a Java JUnit test context, all using Oracle Database Free. By the end of this article, you should feel comfortable building comprehensive database tests for your Oracle Database Java applications. I personally use Testcontainers almost religiously, incorporating it in almost every sample, POC, and product I build.
Want to skip to the code? Check out my Testcontainers samples on GitHub.
You’ll need basic proficiency with Java and a Docker-compatible environment to follow along with this article.
Testcontainers Dependencies
To use Testcontainers in a plain Java project, add the following dependencies to your pom.xml:
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>${oracle.version}</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp</artifactId>
<version>${oracle.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-oracle-free</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>Or, if you’re using Gradle:
implementation "com.oracle.database.jdbc:ojdbc11:${oracleVersion}"
implementation "com.oracle.database.jdbc:ucp:${oracleVersion}"
testImplementation "org.junit.jupiter:junit-jupiter:${junit_version}"
testImplementation "org.testcontainers:testcontainers-junit-jupiter:${testcontainers_version}"
testImplementation "org.testcontainers:testcontainers:${testcontainers_version}"
testImplementation "org.testcontainers:testcontainers-oracle-free:${testcontainers_version}"Spring Boot Dependencies
Spring Boot is works great with Testcontainers, using purpose-built dependencies. If your project is using Spring Boot, include the following dependencies to your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-oracle-free</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>If you’re using Spring Boot with Gradle:
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation "org.testcontainers:testcontainers-oracle-free:${testcontainers_version}"
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'Testcontainers Code Samples
The following sections demonstrate common patterns of using Testcontainers with databases — in this case, Oracle Database!
Starting a container and getting a database connection
To begin, we’ll implement the most basic kind of database test with Testcontainers — starting a database container, and grabbing a connection. This kind of test will be the backbone of application tests that use Testcontainers and Oracle Database.
The GetDatabaseConnectionTest does the following:
- The @Testcontainers annotation marks this as a Testcontainers test, enabling us to start up containers from a test context.
- Create a Testcontainers OracleContainer instance that pulls the Oracle Database Free gvenzl/oracle-free:23.26.0-slim-faststart container image and configure the container to use a throwaway user and password.
- Before the test starts, start the container and configure an OracleDataSource to use the containerized database.
- Implement a test that uses the datasource to get a database connection and query the database version, verifying the database container started correctly and our test implementation works.
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import oracle.jdbc.pool.OracleDataSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.oracle.OracleContainer;
@Testcontainers
public class GetDatabaseConnectionTest {
/**
* Use a containerized Oracle Database instance for testing.
*/
static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.26.0-slim-faststart")
.withStartupTimeout(Duration.ofMinutes(5))
.withUsername("testuser")
.withPassword("testpwd");
static OracleDataSource ds;
@BeforeAll
static void setUp() throws SQLException {
oracleContainer.start();
// Configure the OracleDataSource to use the database container
ds = new OracleDataSource();
ds.setURL(oracleContainer.getJdbcUrl());
ds.setUser(oracleContainer.getUsername());
ds.setPassword(oracleContainer.getPassword());
}
/**
* Verifies the containerized database connection.
* @throws SQLException
*/
@Test
void getConnection() throws SQLException {
// Query Database version to verify connection
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement()) {
stmt.executeQuery("select * from v$version");
}
}
}Of course, application tests will be much more complex than the GetDatabaseConnectionTest — but this provides an example to build upon with custom logic.
Initializing the database with a tables and user data (DDL + DML)
Often in our tests, we need to run DDL and DML scripts to initialize the database. With Testcontainers, configure one or more scripts in the test/resources directory tha run at database startup in the default schema.
The InitializedDatabaseTest runs a students.sql script to create students, courses, and enrollments tables, populating them with some test data. This is simply done by calling the withInitScript method on the OracleContainer instance.
The test verifies the script was run by querying a student by name, and asserting the row is present.
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import oracle.jdbc.pool.OracleDataSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.oracle.OracleContainer;
import static org.assertj.core.api.Assertions.assertThat;
public class InitializedDatabaseTest {
/**
* Use a containerized Oracle Database instance for testing.
*/
static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.26.0-slim-faststart")
.withStartupTimeout(Duration.ofMinutes(5))
.withUsername("testuser")
.withPassword("testpwd")
.withInitScript("students.sql");
static OracleDataSource ds;
@BeforeAll
static void setUp() throws SQLException {
oracleContainer.start();
// Configure the OracleDataSource to use the database container
ds = new OracleDataSource();
ds.setURL(oracleContainer.getJdbcUrl());
ds.setUser(oracleContainer.getUsername());
ds.setPassword(oracleContainer.getPassword());
}
/**
* Verifies the database is initialized with a student
* @throws SQLException
*/
@Test
void getStudent() throws SQLException {
// Query Database version to verify connection
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from students where first_name = 'Alice'")) {
Assertions.assertTrue(rs.next());
assertThat(rs.getString(2)).isEqualTo("Alice");
}
}
}What about DBA initialization scripts?
If you have scripts that would need to be run by a DBA and not the default schema, Testcontainers allows mounting scripts on the database container and executing them as sysdba.
The SyadbaInitTest copies a dbainit.sql with grants and other higher level authorization statements onto the database container, and then executes it with SQLPlus as sysdba.
oracleContainer.copyFileToContainer(MountableFile.forClasspathResource("dbainit.sql"), "/tmp/init.sql");
oracleContainer.execInContainer("sqlplus", "sys / as sysdba", "@/tmp/init.sql");Note that the default session container with Oracle Database Free is freepdb1. When running scripts as sysdba, you’ll want to alter the session container as appropriate. The dbainit.sql script does this before running its database statements:
-- Set as appropriate for your database. "freepdb1" is the default PDB in Oracle Database Free
alter session set container = freepdb1;The test is otherwise the same as the GetDatabaseConnection test, so I won’t copy it here in its entirety.
Using Spring? Try this
Spring Boot, Testcontainers, and Oracle Database are easily combined, letting you holistically test your Oracle Database Spring Boot apps.
The SpringBootDatabaseTest provides a sample Spring Boot test that automatically configures Spring DataSource parameters via the @ServiceConnection annotation.
package com.example;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.oracle.OracleContainer;
@Testcontainers
@SpringBootTest
public class SpringBootDatabaseTest {
/**
* Use a containerized Oracle Database instance for testing.
*/
@Container
@ServiceConnection
static OracleContainer oracleContainer = new OracleContainer("gvenzl/oracle-free:23.26.0-slim-faststart")
.withStartupTimeout(Duration.ofMinutes(5))
.withUsername("testuser")
.withPassword("testpwd");
@Autowired
DataSource dataSource;
@Test
void springDatasourceConnection() throws SQLException {
// Query Database version to verify Spring DataSource connection
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
stmt.executeQuery("select * from v$version");
}
}
}Starting and stopping a database for every test is slow, can I reuse an instance?
One or more database test classes can run against the same Oracle Database container by utilizing Testcontainers’ reusable containers. Reusuable containers are advantageous for faster test startup time, at the cost of potential cross-contamination of test data between disparate suites.
The ReusableDatabaseTest statically configures and starts a reusable database container, which is shared by child test classes.
To enable Testcontainers reuse, ensure the .testcontainers.properties file in your home directoy contains the following parameter (you may need to create this file):
testcontainers.reuse.enable=trueOr set the TESTCONTAINERS_REUSE_ENABLE=true environment variable.
When the reusable tests are run at the package level, the ReusableDatabaseTest spins up an Oracle Database container and the child ReusableSelectTest and ReusableVersionTest classes share and run tests against that container.
The resuable pattern is extensible for any number of tests, greatly reduc startup times for larger test suites, but care must be taken to ioslate test data so idempotency is maintained whether the tests are run as a group or in isolation.
Questions? Let me know in the comments, or reach out on LinkedIn!

Leave a Reply