JUnit and Integration tests are some of the most important parts of CI/CD. Well-written tests with more and more code coverage can detect bugs early and help to improve the quality of the product.
Although Java and Spring have many open source libraries and frameworks for testing code using mocking techniques or embedded versions of middleware and software, and those are used very widely.
This testing can be improved further by using actual software and middleware. For this Testcontainers provides a very elegant, clean library to write Unit and Integration tests using docker containers. Testcontainers provide specialized modules for almost all databases, NoSql, and other components. It also provides a GenericContainer component in which can pull and run any container to test your code.
You can set up Docker on your local development PC by installing docker or you can also use remote docker to test your code.
In the case of Local installation, the library will automatically connect to local docker using the following strategies in order:
Environment variables:
- DOCKER_HOST
- DOCKER_TLS_VERIFY
- DOCKER_CERT_PATH
Defaults Values
- DOCKER_HOST=https://localhost:2376
- DOCKER_TLS_VERIFY=1
- DOCKER_CERT_PATH=~/.docker
If Docker Machine is installed, the docker-machine environment for the first machine is found. Docker Machine needs to be on the PATH for this to succeed.
Setting up Docker Service with TCP socket in Linux for Remote connection
Use the following steps to configure the docker service to listen on TCP port 2375
Create daemon.json
file in /etc/docker
directory and add following
{"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]}
Create the following file with the text
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
/etc/systemd/system/docker.service.d/override.conf
Reload the systemd daemon:
systemctl daemon-reload
Restart the Docker service
systemctl restart docker.service
Now your docker service is ready to connect from a remote client, don’t forget to open port 2375.
Now on the development PC create environment variable
DOCKER_HOST
with the value tcp://{0.0.0.0}:2375, replace 0.0.0.0 with actual docker server IP.
Now Testcontainers will connect to the remote Docker machine.
Testcontainers Basic Configuration in Spring Boot Test
To use Testcontainers in Spring boot application for Junit and Integration testing. First, you need to add the required test dependencies as given below, these are basic dependencies to use Testcontainers in spring boot.
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.5'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.junit.platform:junit-platform-commons:1.8.1'
testImplementation 'org.testcontainers:junit-jupiter:1.16.2'
testImplementation
testAnnotationProcessor 'org.projectlombok:lombok:1.18.22'
Apart from this, you need to add dependencies for the software you need to test. You can refer Modules section of the https://www.testcontainers.org/, which has almost every docker module like specialized images and generic versions of images.
Example of MySql Testcontainers test
To add MySQL use the following Gradle dependency.
testImplementation 'org.testcontainers:mysql:1.16.2'
Define all Spring Datasource properties using java code as given below using @DynamicPropertySource. Static Datasource properties you can keep in the application.properties or use as per your code style.
@SpringBootTest(classes = TokenServiceApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Testcontainers
public abstract class AbstractIntegrationTest {
@Autowired
MockMvc mockMvc;
@Container
private static MySQLContainer database = new MySQLContainer("mysql");
@DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url",database::getJdbcUrl);
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
}
}
Example of Cassandra and Memcached Testcontainers test
Use the following Gradle dependencies. For redis, we will use a generic container.
testImplementation 'org.testcontainers:cassandra:1.16.2'
Define all Spring Datasource properties using java code as given below using @DynamicPropertySource.
@SpringBootTest(classes = RedirectServiceApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Testcontainers
public abstract class AbstractIntegrationTest {
@Autowired
MockMvc mockMvc;
@Container
public static final CassandraContainer cassandra
= (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);
@Container
public static final GenericContainer redis
= (GenericContainer) new GenericContainer("redis:3.0.6").withExposedPorts(6379);
static ClientAndServer mockServer;
@DynamicPropertySource
static void setupCassandraConnectionProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.cassandra.keyspace-name", "shorturldb"::toString);
registry.add("spring.data.cassandra.contact-points", cassandra::getContainerIpAddress);
registry.add("spring.data.cassandra.port", String.valueOf(cassandra.getMappedPort(9042))::toString);
registry.add("spring.data.cassandra.local-datacenter", "datacenter1"::toString);
registry.add("spring.data.cassandra.schema-action", "create_if_not_exists"::toString);
createKeyspace(cassandra.getCluster());
mockServer = startClientAndServer(8089);
createExpectationForGetToken();
registry.add("spring.redis.database", "0"::toString);
registry.add("spring.redis.host", redis::getContainerIpAddress);
registry.add("spring.redis.port", String.valueOf(redis.getMappedPort(6379))::toString);
registry.add("spring.redis.timeout", "60000"::toString);
redis.start();
}
@AfterAll
public static void stopServer() {
mockServer.stop();
redis.start();
}
static void createKeyspace(Cluster cluster) {
try(Session session = cluster.connect()) {
session.execute("CREATE KEYSPACE IF NOT EXISTS shorturldb WITH replication = \n" +
"{'class':'SimpleStrategy','replication_factor':'1'};");
}
}
}
You can refer following Github repository for working examples https://github.com/sheelprabhakar/url-shortener-msa.