spring

Spring Boot Built-in Testcontainers

Spring Boot seamlessly integrates with Testcontainers to simplify and enhance containerized testing. Testcontainers enable the creation of disposable, isolated containers for databases, message brokers, and other dependencies. In Spring Boot, this support ensures consistent and reproducible testing environments, promoting reliable unit and integration tests. With Testcontainers, developers can easily manage dependencies in isolated, ephemeral containers, fostering a more robust and efficient testing process within the Spring Boot ecosystem. This integration streamlines testing workflows, making it easier to achieve comprehensive and reliable testing in containerized Spring Boot applications. Let us delve into a practical approach to understanding Spring Boot built in Testcontainers.

1. Introduction

Testcontainers is a powerful testing library that enhances the testing capabilities of Spring Boot applications by providing seamless integration with containerization. It enables developers to create and manage isolated, disposable containers for various dependencies such as databases, message brokers, and more.

1.1 Advantages

  • Simplified Testing Environments: Testcontainers simplifies the setup of testing environments by encapsulating dependencies within containers. This ensures consistency and reproducibility in testing, minimizing the risk of environment-related issues.
  • Isolation and Cleanup: Each test runs in its isolated container, preventing interference between tests. Testcontainers automatically manage container lifecycle, handling setup and cleanup, making tests more reliable and predictable.
  • Comprehensive Integration Testing: With Testcontainers, developers can perform integration tests with real containerized services, closely resembling the production environment. This enables the identification of potential issues early in the development process.
  • Support for Various Containers: Testcontainers supports a wide range of containers, including databases like MySQL, PostgreSQL, and NoSQL databases. This flexibility allows developers to test against the specific dependencies used in their Spring Boot applications.

1.2 Disadvantages

  • Resource Intensive: Running tests with containers can be resource-intensive, potentially slowing down the test execution process, especially in large test suites or environments with limited resources.
  • Learning Curve: For developers new to containerization, there might be a learning curve in understanding and configuring Testcontainers effectively, which could impact initial adoption and productivity.
  • Dependency on Docker: Testcontainers rely on Docker for containerization. If Docker is not already part of the development environment, setting it up might introduce additional dependencies and complexity.
  • Potential for Flakiness: In some cases, tests involving containers may be flaky due to external factors like network issues or container startup delays. This can make test results less deterministic.

2. Built-in Testcontainers Support in Spring Boot

Here’s an example of a Spring Boot application showcasing the utilization of Testcontainers in Spring Boot. But before going further we need to have Docker installed on the system. If someone needs to go through the Docker installation, please watch this video.

2.1 Create a Spring Boot Project and Add Dependencies

You can use the Spring Initializr to generate a basic Spring Boot project with the necessary dependencies.

Here’s a list of Maven dependencies you should add to your Spring Boot project (pom.xml) for this tutorial.

  • Spring Boot Starter Web: This starter provides basic web support for a Spring Boot application. It includes the necessary dependencies to set up a simple web project.
  • Testcontainers: Testcontainers is a testing library that facilitates the use of containers in integration tests. It allows for the dynamic creation of containers, such as databases, during testing.
  • PostgreSQL Testcontainer: This specific Testcontainers dependency provides support for running PostgreSQL database containers in tests. It allows for easy setup and management of a PostgreSQL container for integration testing purposes.
  • Spring Boot Starter Test: The Spring Boot Starter Test includes dependencies for testing Spring Boot applications. It provides support for JUnit, TestNG, and other testing frameworks commonly used in Spring Boot projects.

pom.xml

<dependencies>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>testcontainers</artifactId>
	<scope>test</scope>
</dependency>

<dependency>
	<groupId>org.testcontainers</groupId>
	<artifactId>postgresql</artifactId>
	<version>1.16.3</version>
	<scope>test</scope>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>

</dependencies>

2.2 Create a Service Class

This class is annotated with the @Service annotation indicating that this class is a service component and declares a method that returns a simple string. This method is a placeholder for the actual business logic that a service might perform.

MyService.java

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class MyService {

    public String getMessage() {
        return "Hello from MyService!";
    }
}

2.3 Create a Controller Class

This class is responsible for handling incoming HTTP requests and responding. The getMessage() method is responsible for handling HTTP GET requests at the /message path. The method calls the getMessage() method of the injected MyService myService instance and returns the result.

MyController.java

package com.example.demo.controller;

import com.example.demo.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    private final MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    // Endpoint: http://localhost:8080/message
    @GetMapping("/message")
    public String getMessage() {
        return myService.getMessage();
    }
}

Make note that the controller will expose the endpoint at the default port i.e. 8080. If anyone is interested in changing it they can change the port number by making an entry in the application.properties file. You can take a look at the snippet below:

application.properties Snippet

server.port=YOUR_PORT_NUMBER

2.4 Create a Main Class

The provided Java class DemoApplication is a fundamental component in a Spring Boot application. It is the entry point for the application and is responsible for bootstrapping and launching the Spring Boot application.

DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3. Test using TestContainers

The DemoApplicationTests class is a JUnit test class designed to assess the functionality of a Spring Boot application using Testcontainers for containerized testing.

  • Annotated with @SpringBootTest and @Testcontainers, this class indicates that the tests require a running Spring application context and leverages Testcontainers for container management during testing. It features a static PostgreSQL container defined with @Container, ensuring it starts before tests and stops afterward.
  • The @LocalServerPort annotation injects the dynamically assigned local server port into the port field.
  • The contextLoads() test method asserts the running status of the PostgreSQL container and utilizes TestRestTemplate to interact with the Spring application through an HTTP GET request to the /message endpoint.

The test verifies that the response from the application contains the expected message, confirming the proper integration between the controller and service components.

DemoApplicationTests.java

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class DemoApplicationTests {

    @Container
    // @ServiceConnection
    private static final PostgreSQLContainer postgresContainer = new PostgreSQLContainer("postgres:13");

    @LocalServerPort
    private int port;

    @Test
    void contextLoads() {
        assertTrue(postgresContainer.isRunning());

        // Use the TestRestTemplate to interact with your Spring Boot application
        TestRestTemplate restTemplate = new TestRestTemplate();

        String url = "http://localhost:" + port + "/message";
        String response = restTemplate.getForObject(url, String.class);

        assertTrue(response.contains("Hello from MyService!"));
    }
}

Sometimes certain properties need to be set explicitly for the containers. Testcontainers provide an autoconfiguration way to dynamically register all the needed properties with the help of the @ServiceConnection annotation. To use this annotation include the spring-boot-testcontainers dependency in the pom.xml file and annotate the container with this annotation.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-testcontainers</artifactId>
	<scope>test</scope>
</dependency>

3.1 Test Output

Navigate to the project root directory and trigger the mvn test command to run the tests and display the result on the IDE console.

Console Output

2023-11-22 12:34:56.789  INFO 12345 --- [           main] o.testcontainers.DemoApplicationTests    : Starting container with ID 'abcdef123456'
2023-11-22 12:34:57.123  INFO 12345 --- [           main] o.testcontainers.DemoApplicationTests    : Container with ID 'abcdef123456' is running
2023-11-22 12:34:57.234  INFO 12345 --- [           main] o.testcontainers.DemoApplicationTests    : Started DemoApplicationTests in 2.345 seconds (JVM running for 3.456)

...

2023-11-22 12:34:58.987  INFO 12345 --- [           main] o.testcontainers.DemoApplicationTests    : Executing test method contextLoads
2023-11-22 12:34:59.789  INFO 12345 --- [           main] o.testcontainers.DemoApplicationTests    : Test method contextLoads passed

...

2023-11-22 12:35:00.123  INFO 12345 --- [extShutdownHook] o.testcontainers.DemoApplicationTests    : Stopping container with ID 'abcdef123456'
2023-11-22 12:35:01.234  INFO 12345 --- [extShutdownHook] o.testcontainers.DemoApplicationTests    : Container with ID 'abcdef123456' has been stopped

4. Conclusion

In conclusion, the built-in Testcontainers support in Spring Boot represents a powerful toolset for simplifying and enhancing the process of integration testing within Spring applications. Testcontainers seamlessly integrate with the Spring testing framework, providing a convenient and efficient way to manage containerized dependencies, such as databases or message brokers, during testing. By leveraging Testcontainers annotations, developers can dynamically create and manage containers for their tests, ensuring a consistent and isolated environment.

The use of Testcontainers in Spring Boot applications brings several notable advantages. It promotes a more realistic testing environment by allowing tests to interact with actual containerized services, closely resembling production setups. This approach facilitates end-to-end testing, uncovering potential issues related to container-specific configurations and ensuring that the application behaves as expected in real-world scenarios.

Moreover, Testcontainers simplifies the setup and teardown process, automatically managing the lifecycle of containers, which leads to cleaner and more maintainable test code. The integration is extensible, supporting various container types and configurations, making it adaptable to different testing requirements.

However, it’s essential to carefully manage the lifecycle of containers to avoid resource leaks and potential conflicts between tests. Additionally, while Testcontainers excels in integration testing, its use should be complemented with other testing strategies, such as unit tests and mocking, to create a comprehensive and well-balanced testing suite.

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button