Integration tests helps in ensuring the functionality are working as expected for an application. Testcontainers is a opensource framework, that provides a library to easily create required test environment(database, message queues, etc.) by generating Docker containers. In this article, we learn how to use the Testcontainers for testing the functionalities of a PostgreSQL based Springboot REST application.

Table of Contents
- Creating the Springboot REST API
- Creating required REST endpoints
- Creating Springboot tests with Testcontainers
- Conclusion
Creating the Springboot REST API
Let’s create a Simple Spring boot REST application. We can add the org.testcontainer:postgresql maven dependency, which supports in containerizing the PostgreSQL database for our integration test.
Below is the dependencies from pom.xml file.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.asbnotebook</groupId> <artifactId>spring-boot-testcontainer-example</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-testcontainer-example</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Creating required REST endpoints
Let’s now add an entity class and create required JPA repository interface.
Creating the entity class
Let’s create a Employee entity class.
@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer salary;
}
Create a JPA repository as shown below.
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
Create a DTO class with the name EmployeeDto, as shown below.
@Data
@Builder(toBuilder = true)
public class EmployeeDto {
private Long id;
private String name;
private Integer salary;
}
Creating the REST endpoints
Create a Rest API layer by adding the EmployeeController java class.
Below are the API details:
- /employees: A HTTP Get API. Returns all the employees from the database.
- /employee: A HTTP Post API. Creates a new employee in the database.
- /employee/{id}: A HTTP Delete API. Deletes the employee from the database.
@Slf4j
@RestController
public class EmployeeController {
@Autowired
EmployeeRepository employeeRepository;
@GetMapping("/employees")
public ResponseEntity<List<EmployeeDto>> getAllEmployees() {
var employees = employeeRepository.findAll().stream()
.map(entityToDto())
.collect(Collectors.toList());
return new ResponseEntity<>(employees, HttpStatus.OK);
}
private static Function<Employee, EmployeeDto> entityToDto() {
return employee -> EmployeeDto.builder()
.id(employee.getId())
.name(employee.getName())
.salary(employee.getSalary()).build();
}
@PostMapping("/employee")
public ResponseEntity<EmployeeDto> save(@RequestBody EmployeeDto employeeDto) {
log.info("Post request called with EmployeeDTO: {}", employeeDto);
var employee = Employee.builder()
.name(employeeDto.getName())
.salary(employeeDto.getSalary())
.build();
var empFromDb = employeeRepository.save(employee);
employeeDto.setId(empFromDb.getId());
log.info("Created employee in DB: {}", employeeDto);
return new ResponseEntity<>(employeeDto, HttpStatus.CREATED);
}
@DeleteMapping("/employee/{id}")
public ResponseEntity<Void> save(@PathVariable("id") Long empId) {
log.info("Delete request for employeeID:{}", empId);
employeeRepository.deleteById(empId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Configuring the application
We also need to add below Spring boot configuration properties to the src/main/resources/application.properties file.
The below properties are used to configure the database connection with the PostgreSQL database.
spring.application.name=spring-boot-testcontainer-example spring.datasource.url=jdbc:postgresql://localhost:5432/employeedb spring.datasource.username=postgres spring.datasource.password=postgres spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
Creating Springboot tests with Testcontainers
Let’s test our REST endpoints now. We use Testcontainers to create a dynamic PostgreSQL container.
The application uses this container while running the tests. Once the test execution completed, the container is also cleaned up automatically from the docker environment.
Initializing the database
Create a init.sql under the folder src/test/resources folder. Here, the script create a new database called employeedb.
CREATE DATABASE employeedb;
Create a test configuration class
Let’s create an abstract test configuration class. This class initializes the Testcontainers by creating a PostgreSQL docker container.
During the test execution, we generate the JDBC URL using the PostgreSQL docker container’s host and port and pass the configuration to the spring boot application.
Also, Notice that:
- The @Container annotation helps in managing the lifecycle of the docker container.
- The parse() method helps in specifying the docker image.
- The withExposedPorts() method exposes the port(5432 in our case) outside the container.
- The withClasspathResourceMapping() method copies the resources into the container from the classpath resource directory(src/test/resources folder).
- Once the docker container starts, we generate the the JDBC connection string using the getFormattedConnectionString() method. Here, we use the docker containers host and port.
@Testcontainers
abstract class BaseTest {
private static final Integer PORT = 5432;
private static final String DB_USER = "test";
private static final String DB_PASSWORD = "test";
@Container
private static final PostgreSQLContainer<?> postgresContainer =
new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"))
.withExposedPorts(PORT)
.withUsername(DB_USER)
.withPassword(DB_PASSWORD)
.withClasspathResourceMapping("init.sql",
"/docker-entrypoint-initdb.d/init.sql",
BindMode.READ_ONLY);
@DynamicPropertySource
static void postgresProperties(DynamicPropertyRegistry registry){
postgresContainer.start();
registry.add("spring.datasource.url", BaseTest::getFormattedConnectionString);
registry.add("spring.datasource.username", ()-> DB_USER);
registry.add("spring.datasource.password", ()-> DB_PASSWORD);
}
private static String getFormattedConnectionString() {
return String.format("jdbc:postgresql://%s:%s/employeedb",
postgresContainer.getHost(),
postgresContainer.getFirstMappedPort());
}
}
Adding the tests to validate functionality
Let’s create a EmployeeServiceIntegrationTest class, which extends the BaseTest abstract class.
The class is annotated with the @SpringBootTest annotation. This helps in auto-configuring the spring boot application while running the tests.
The spring boot application is initialized with the updated database configurations and connects to the Testcontainer based PostgeSQL docker container, which we have defined in the BaseTest abstract class.
We are also utilizing the TestRestTemplate instance to invoke the REST APIs.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EmployeeServiceIntegrationTest extends BaseTest {
@Autowired
TestRestTemplate testRestTemplate;
@Autowired
EmployeeRepository employeeRepository;
@AfterEach
void clearRecordsFromDb() {
employeeRepository.deleteAll();
}
@Test
void testFindAllEmployees() {
var employeeDto1 = EmployeeDto.builder().name("Arun").salary(20000).build();
var employeeDto2 = EmployeeDto.builder().name("Raj").salary(10000).build();
//Create employee details in DB:
testRestTemplate.postForEntity("/employee", employeeDto1, EmployeeDto.class);
testRestTemplate.postForEntity("/employee", employeeDto2, EmployeeDto.class);
//fetch all employees:
var response = testRestTemplate.getForEntity("/employees", String.class);
//Verify response:
var employeesResponseJSon = response.getBody();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
DocumentContext resp = JsonPath.parse(employeesResponseJSon);
assertEquals(Optional.ofNullable(resp.read("$.length()")).get(), 2);
assertEquals(Optional.ofNullable(resp.read("$[0].id")).get(), 1);
assertEquals(Optional.ofNullable(resp.read("$[0].name")).get(), "Arun");
assertEquals(Optional.ofNullable(resp.read("$[0].salary")).get(), 20000);
assertEquals(Optional.ofNullable(resp.read("$[1].id")).get(), 2);
assertEquals(Optional.ofNullable(resp.read("$[1].name")).get(), "Raj");
assertEquals(Optional.ofNullable(resp.read("$[1].salary")).get(), 10000);
}
@Test
void testDeleteEmployees() {
//Create employee details in DB:
var employeeDto1 = EmployeeDto.builder().name("Arun").salary(20000).build();
var employee = testRestTemplate.postForEntity("/employee", employeeDto1, EmployeeDto.class).getBody();
//fetch all employees:
assertEquals(Optional.ofNullable(JsonPath.parse(testRestTemplate.getForEntity("/employees", String.class)
.getBody()).read("$.length()")).get(), 1);
//Delete record:
testRestTemplate.delete("/employee/{id}", employee.getId());
//fetch all employees:
assertEquals(Optional.ofNullable(JsonPath.parse(testRestTemplate.getForEntity("/employees", String.class)
.getBody()).read("$.length()")).get(), 0);
}
}
Conclusion
In this post, we learned how Testcontainers can be utilized in writing the integration tests for the Springboot application, that uses PostgreSQL as database.
Code sample is available on Github.
Discover more from ASB Notebook
Subscribe to get the latest posts sent to your email.