DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Data: Data Auditing Using JaVers and MongoDB
  • Best Performance Practices for Hibernate 5 and Spring Boot 2 (Part 4)
  • Keep Your Application Secrets Secret
  • Testcontainers With Kotlin and Spring Data R2DBC

Trending

  • How to Submit a Post to DZone
  • A Complete Guide to Modern AI Developer Tools
  • Medallion Architecture: Why You Need It and How To Implement It With ClickHouse
  • Top Book Picks for Site Reliability Engineers
  1. DZone
  2. Data Engineering
  3. Databases
  4. Testcontainers and Spring Boot

Testcontainers and Spring Boot

Spring Boot and TestContainers: A match made in heaven? Let's see how you can utilize both to help use containers in your unit tests.

By 
Biju Kunjummen user avatar
Biju Kunjummen
·
May. 30, 18 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
43.0K Views

Join the DZone community and get the full member experience.

Join For Free

Testcontainers is just awesome! It provides a very convenient way to start up and CLEANLY tear down docker containers in JUnit tests. This feature is very useful for integration testing of applications against real databases and any other resource for which a docker image is available.

My objective is to demonstrate a sample test for a JPA based Spring Boot Application using Testcontainers. The sample is based on an example at the Testcontainer GitHub repo.

Sample App

The Spring Boot based application is straightforward. It is a Spring Data JPA based application with the web layer written using Spring Web Flux. The entire sample is available at my GitHub repo and it may be easier to just follow the code directly there.

The City entity being persisted looks like this (using Kotlin):

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
data class City(
        @Id @GeneratedValue var id: Long? = null,
        val name: String,
        val country: String,
        val pop: Long
) {
    constructor() : this(id = null, name = "", country = "", pop = 0L)
}


All that is needed to provide a repository to manage this entity is the following interface, thanks to the excellent Spring Data JPA project:

import org.springframework.data.jpa.repository.JpaRepository
import samples.geo.domain.City

interface CityRepo: JpaRepository<City, Long>


I will not cover the web layer here as it is not relevant to the discussion.

Testing the Repository

Spring Boot provides a feature called the Slice tests, which is a neat way to test different horizontal slices of the application. A test for the CityRepo repository looks like this:


import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import samples.geo.domain.City;
import samples.geo.repo.CityRepo;

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

@RunWith(SpringRunner.class)
@DataJpaTest
public class CitiesWithEmbeddedDbTest {

    @Autowired
    private CityRepo cityRepo;

    @Test
    public void testWithDb() {
        City city1 = cityRepo.save(new City(null, "city1", "USA", 20000L));
        City city2 = cityRepo.save(new City(null, "city2", "USA", 40000L));

        assertThat(city1)
                .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L);

        assertThat(city2)
                .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L);

        assertThat(cityRepo.findAll()).containsExactly(city1, city2);
    }

}


The"@DataJpaTest" annotation starts up an embedded h2 databases, configures JPA, and loads up any Spring Data JPA repositories(CityRepo in this instance).

This kind of a test works well, considering that JPA provides the database abstraction, and if JPA is used correctly, the code should be portable across any supported databases. However, assuming that this application is expected to be run against a PostgreSQL in production, ideally, there would be some level of integration testing done against the database, which is where TestContainer fits in. It provides a way to boot up PostgreSQL as a Docker container.

Testcontainers

The same repository test using Testcontainers looks like this:

import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.testcontainers.containers.PostgreSQLContainer;
import samples.geo.domain.City;
import samples.geo.repo.CityRepo;

import java.time.Duration;

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

@RunWith(SpringRunner.class)
@DataJpaTest
@ContextConfiguration(initializers = {CitiesWithPostgresContainerTest.Initializer.class})
public class CitiesWithPostgresContainerTest {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer =
            (PostgreSQLContainer) new PostgreSQLContainer("postgres:10.4")
                    .withDatabaseName("sampledb")
                    .withUsername("sampleuser")
                    .withPassword("samplepwd")
                    .withStartupTimeout(Duration.ofSeconds(600));

    @Autowired
    private CityRepo cityRepo;

    @Test
    public void testWithDb() {
        City city1 = cityRepo.save(new City(null, "city1", "USA", 20000L));
        City city2 = cityRepo.save(new City(null, "city2", "USA", 40000L));

        assertThat(city1)
                .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L);

        assertThat(city2)
                .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L);

        assertThat(cityRepo.findAll()).containsExactly(city1, city2);
    }

    static class Initializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
                    "spring.datasource.username=" + postgreSQLContainer.getUsername(),
                    "spring.datasource.password=" + postgreSQLContainer.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}


The core of the code looks the same as the previous test, but the repository here is being tested against a real PostgreSQL database. To go into a little more detail:

A PostgreSQL container is being started up using a JUnit Class Rule, which gets triggered before any of the tests are run. This dependency is being pulled in using a Gradle dependency of the following type:

    testCompile("org.testcontainers:postgresql:1.7.3")


The class rule starts up a PostgreSQL docker container(postgres:10.4) and configures a database and credentials for the database. Now from Spring Boot's perspective, these details need to be passed on the application as properties BEFORE Spring starts creating a test context for the test to run in, and this is done for the test using an ApplicationContextInitializer, this is invoked by Spring very early in the lifecycle of a Spring Context.

The custom ApplicationContextInitializer, which sets the database name, URL, and user credentials is hooked up to the test using this code:

...
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
...

@RunWith(SpringRunner.class)
@DataJpaTest
@ContextConfiguration(initializers = {CitiesWithPostgresContainerTest.Initializer.class})
public class CitiesWithPostgresContainerTest {
...


With this boilerplate set up in place, Testcontainer and Spring Boot slice test will take over running the test. More importantly, Testcontainers also takes care of tear down and the JUnit Class Rule ensures that once the test is complete, the containers are stopped and removed.

Conclusion

This was a whirlwind tour of Testcontainers, and there is far more to Testcontainers than what I have covered here, but I hope this provides a taste for what is feasible using this excellent library and how to configure it with Spring Boot.

Spring Framework Spring Boot Database Testing application Docker (software) Spring Data

Published at DZone with permission of Biju Kunjummen, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Data: Data Auditing Using JaVers and MongoDB
  • Best Performance Practices for Hibernate 5 and Spring Boot 2 (Part 4)
  • Keep Your Application Secrets Secret
  • Testcontainers With Kotlin and Spring Data R2DBC

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!