AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

testcontainers with Spring Boot

To test our Spring Boot app with Spring Data, we often use an embedded in-memory database like H2. Although this is a very valid approach on occasion, we may want to test against a real database as much as possible. This tutorial shows a testcontainers example with Spring Boot against PostgreSQL.

What is Testcontainers?

Testcontainers is a set of libraries written and created with the aim of creating integration and end-to-end tests.

Docker enables infrastructure creation with databases and messaging systems for Spring Boot integration testing, e.g., Kafka or databases. So Testconatiners use them to create containers for testing.

What is Integration Test?

Integration tests or Integration Test are that part of testing our applications where modules are logically integrated and tested as a group. The objective of this type of test is to see and analyze possible defects and errors in the interaction between different layers and to see how they integrate with each other.

How to use Testcontainers with Spring Boot?

To test Testcontainers, we will start with an example that consist of a Spring Boot application that uses Spring Data to connect to a PostgreSQL database.

To be able to use Testcontainers, the first step is to add its dependency to the pom.xml file:

Dependencias Maven para Testcontainers

<dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>

Configuring Testcontainers in Spring Boot by class

Use containerized PostgreSQL in class; create and set connection parameters for container:

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class CarRepositoryPropertiesIntegrationTest {
  @Autowired
  private CarRepository carRepository;
  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
  }
  @Container
  public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11-alpine")
      .withDatabaseName("integration-tests-db")
      .withPassword("inmemory")
      .withUsername("inmemory");
  @Test
  public void save_and_read_book() {
    Car car = new Car();
    car.setDescription("New car");
    carRepository.save(car);
    List<Car> allCars = carRepository.findAll();
    assert !allCars.isEmpty();
    assert allCars.get(0).getDescription().equalsIgnoreCase("New car");
    assert allCars.size() == 1;
  }
}

As we can see in our previous class, we used the Spring Boot annotation @SpringBootTest for context initialization, and three more annotations to create and instantiate a container, in this case, PostgreSQL. The two annotations we used from testcontainers are:

  • @AutoConfigureTestDatabase, an annotation to configure the tests with JUnit and indicate by parameter the non-configuration of the database.
  • @Testcontainers, which indicates to our class that we will use Testcontainers and in this way it can instantiate a container.
  • @Container, we indicate through a static variable the configuration parameters of our container.

With this approach, we will create an instance of the database for each class, and once all the tests of that class are finished, that instance will be deleted. This approach is very good because it allows greater independence and isolation between tests, but it creates a much more verbose code with many more lines of code.

In addition, we will have to add a last configuration so that our test works correctly, add a property in our application.yml with the Testcontainers configuration URL. This application.yml must be in the resources folder of our test. Consider indicating test profile in file if starting tests with profile:

spring.datasource.url=jdbc:tc:postgresql:11.1:///integration-tests-db

URL includes “tc:”, PostgreSQL version, and database name for Testcontainers.

Create a Testcontainer instance to share across all tests

Regarding the use of Testcontainers, we can see two different approaches. The first one (seen in the previous section) would create an instance of the database per class; and the second one (which we will see next), would create a single instance of the database for all tests to share.

To create this common class for all our test classes, we will extend PostgreSQLContainer. This will provide us with the necessary methods to add configuration to our container.

public class CommonPostgresqlContainer extends PostgreSQLContainer<CommonPostgresqlContainer> {
  private static final String VERSION = "postgres:11.1";
  private static CommonPostgresqlContainer container;
  private CommonPostgresqlContainer() {
    super(VERSION);
  }
  public static CommonPostgresqlContainer getInstance() {
    if (container == null) {
      container = new CommonPostgresqlContainer();
    }
    return container;
  }
  @Override
  public void start() {
    super.start();
    System.setProperty("DB_URL", container.getJdbcUrl()); 
    System.setProperty("DB_USERNAME", container.getUsername());
    System.setProperty("DB_PASSWORD", container.getPassword());
  }
  @Override
  public void stop() {
    //do nothing, JVM handles shut down
  }
}

We will add the necessary configuration in the new class to instantiate it in all classes that use a test with a Database. To do this, we have defined two methods with a start and a stop in which we will add the configuration of our container.

We have left the stop() method empty so that we delegate its removal to the JVM. On the other hand, in the start() method, we have added the configuration to our database. Remember that the spring.datasource.url property must be filled in as follows:

spring.datasource.url=jdbc:tc:postgresql:11.1:///integration-tests-db

To ensure that all our tests make use of the same container, we will initialize it as follows:

@SpringBootTest
public class CarRepositorySharedIntegrationTest {
  @Autowired
  private CarRepository carRepository;
  public static PostgreSQLContainer postgreSQLContainer = CommonPostgresqlContainer.getInstance();
  @Test
  public void save_and_read_Car() {
    Car car = new Car();
    car.setDescription("New car");
    carRepository.save(car);
    List<Car> allCars = carRepository.findAll();
    assert !allCars.isEmpty();
    assert allCars.get(0).getDescription().equalsIgnoreCase("New car");
    assert allCars.size() == 1;
  }
}

To allow our tests to share an instance of our container, we will initialize it with a static variable.

Conclusion

In this article about testcontainers example with Spring Boot we have focused mainly on the part of integration testing with a Database.

Testcontainers simplifies container creation for integration testing. It isolates app layers and provides more realistic testing than H2.

If you want to see the complete example, you can download it from our Github here.

If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!

Leave a Reply

Your email address will not be published. Required fields are marked *