Better Testcontainer Support – Spring Boot 3.1

Testcontainers is an open source framework for providing throwaway, lightweight instances of databases, message brokers, web browsers, or just about anything that can run in a Docker container. Using Testcontainers, tests can be run against real dependencies without need for mock or environment configurations.

In this post we will see how to use Testcontainers for integration tests and at development time.

To get started will create a Spring project from Spring initializr with the required dependencies and define API’s as shown below

@RestController
public class StudentController {

	@Autowired
	StudentService service;
	
	@PostMapping("/Student")
	public Student add(@RequestBody Student student) {
		return service.addStudent(student);
		
	}
	
	@GetMapping("/Student")
	public List<Student> getAll() {
		return service.getAllStudents();
		
	}

}

Have defined two API’s the first one is to add a Student to postgresSQL DB and the next API is to fetch all the students from the DB.

Integration Tests:

Using Testcontainers lets integrate tests these API’s . For this created a postgresSQL container and TestRestTemplate .

package com.example.testcontainers.controller;

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

import java.awt.List;
import java.net.URI;
import java.net.URISyntaxException;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import com.example.testcontainers.entity.Student;

@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
@Testcontainers
class StudentControllerTest {
	
	@LocalServerPort
    int randomServerPort;	

    @Autowired
    private TestRestTemplate restTemplate;
	
	@Container
	@ServiceConnection
	static PostgreSQLContainer<?> container = new PostgreSQLContainer<>("postgres:latest");	

	@Test
	void testAdd() throws URISyntaxException {
		 	final String baseUrl = "http://localhost:"+randomServerPort+"/Student";
	        URI uri = new URI(baseUrl);
	        Student student = Student.builder().rollNumber(20230101).age(18).name("Mike").build();	         
	        HttpHeaders headers = new HttpHeaders();
	        headers.set("X-COM-PERSIST", "true"); 
	        HttpEntity<Student> request = new HttpEntity<>(student, headers);	         
	        ResponseEntity<String> result = this.restTemplate.postForEntity(uri, request, String.class);	         
	        assertEquals(200, result.getStatusCode().value());
	}

	@Test
	void testGetAll() throws URISyntaxException {
		final String baseUrl = "http://localhost:"+randomServerPort+"/Student";
        URI uri = new URI(baseUrl);
        ResponseEntity<Student[]> result = this.restTemplate.getForEntity(uri,Student[].class);     
        assertEquals(200, result.getStatusCode().value());
	}

}

Testcontainers at Development:

Testcontainers can be used during development time without the need for actual dependencies.

@TestConfiguration(proxyBeanMethods = false)
public class TestTestcontainersApplication {

	@Bean
	@ServiceConnection
	PostgreSQLContainer<?> postgresContainer() {
		return new PostgreSQLContainer<>("postgres:latest");
	}

	public static void main(String[] args) {
		SpringApplication.from(TestcontainersApplication::main).with(TestTestcontainersApplication.class).run(args);
	}

}

Using SpringApplication.from method we can delegate to main Spring Boot Application ‘TestcontainersApplication’ with postgresSQL container dependency . By this way we will be able to start the application without real postgresSQL DB and test our changes during development.

@SpringBootApplication
public class TestcontainersApplication {

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

}

Logs showing application started using Testcontainers


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)

2023-07-08T23:11:04.842+05:30  INFO 3096 --- [           main] c.e.t.TestcontainersApplication          : Starting TestcontainersApplication using Java 17.0.1 with PID 3096 (C:\Users\mahen\Downloads\testcontainers\testcontainers\target\classes started by mahen in C:\Users\mahen\Downloads\testcontainers\testcontainers)
2023-07-08T23:11:04.848+05:30  INFO 3096 --- [           main] c.e.t.TestcontainersApplication          : No active profile set, falling back to 1 default profile: "default"
2023-07-08T23:11:05.814+05:30  INFO 3096 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-07-08T23:11:05.911+05:30  INFO 3096 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 83 ms. Found 1 JPA repository interfaces.
2023-07-08T23:11:06.686+05:30  INFO 3096 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-07-08T23:11:06.701+05:30  INFO 3096 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-07-08T23:11:06.702+05:30  INFO 3096 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.10]
2023-07-08T23:11:06.873+05:30  INFO 3096 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-07-08T23:11:06.876+05:30  INFO 3096 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1939 ms
2023-07-08T23:11:07.037+05:30  INFO 3096 --- [           main] o.t.utility.ImageNameSubstitutor         : Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2023-07-08T23:11:07.337+05:30  INFO 3096 --- [           main] o.t.d.DockerClientProviderStrategy       : Loaded org.testcontainers.dockerclient.NpipeSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2023-07-08T23:11:07.683+05:30  INFO 3096 --- [           main] o.t.d.DockerClientProviderStrategy       : Found Docker environment with local Npipe socket (npipe:////./pipe/docker_engine)
2023-07-08T23:11:07.685+05:30  INFO 3096 --- [           main] org.testcontainers.DockerClientFactory   : Docker host IP address is localhost
2023-07-08T23:11:07.722+05:30  INFO 3096 --- [           main] org.testcontainers.DockerClientFactory   : Connected to docker: 
  Server Version: 20.10.17
  API Version: 1.41
  Operating System: Docker Desktop
  Total Memory: 7830 MB
2023-07-08T23:11:07.751+05:30  INFO 3096 --- [           main] tc.testcontainers/ryuk:0.5.1             : Creating container for image: testcontainers/ryuk:0.5.1
2023-07-08T23:11:08.039+05:30  INFO 3096 --- [           main] o.t.utility.RegistryAuthLocator          : Credential helper/store (docker-credential-desktop) does not have credentials for https://index.docker.io/v1/
2023-07-08T23:11:08.196+05:30  INFO 3096 --- [           main] tc.testcontainers/ryuk:0.5.1             : Container testcontainers/ryuk:0.5.1 is starting: ca3c873deb2fac10522bd9c663c570f8b8c3d3418cbeacd3aa98e22f1fe552a9
2023-07-08T23:11:08.987+05:30  INFO 3096 --- [           main] tc.testcontainers/ryuk:0.5.1             : Container testcontainers/ryuk:0.5.1 started in PT1.2537405S
2023-07-08T23:11:08.998+05:30  INFO 3096 --- [           main] o.t.utility.RyukResourceReaper           : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2023-07-08T23:11:08.999+05:30  INFO 3096 --- [           main] org.testcontainers.DockerClientFactory   : Checking the system...
2023-07-08T23:11:09.000+05:30  INFO 3096 --- [           main] org.testcontainers.DockerClientFactory   : ✔︎ Docker server version should be at least 1.6.0
2023-07-08T23:11:09.001+05:30  INFO 3096 --- [           main] tc.postgres:latest                       : Creating container for image: postgres:latest
2023-07-08T23:11:09.062+05:30  INFO 3096 --- [           main] tc.postgres:latest                       : Container postgres:latest is starting: 3dbbe20ac28567a678710e637c779f70b9cca7489641425cdc1621cea2075f72
2023-07-08T23:11:10.826+05:30  INFO 3096 --- [           main] tc.postgres:latest                       : Container postgres:latest started in PT1.8254303S
2023-07-08T23:11:10.827+05:30  INFO 3096 --- [           main] tc.postgres:latest                       : Container is started (JDBC URL: jdbc:postgresql://localhost:51902/test?loggerLevel=OFF)
2023-07-08T23:11:10.901+05:30  INFO 3096 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-07-08T23:11:11.146+05:30  INFO 3096 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@3dfe92ef
2023-07-08T23:11:11.149+05:30  INFO 3096 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-07-08T23:11:11.296+05:30  INFO 3096 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-07-08T23:11:11.393+05:30  INFO 3096 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.5.Final
2023-07-08T23:11:11.396+05:30  INFO 3096 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer
2023-07-08T23:11:11.631+05:30  INFO 3096 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-07-08T23:11:11.795+05:30  INFO 3096 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2023-07-08T23:11:12.067+05:30  INFO 3096 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-07-08T23:11:12.621+05:30  INFO 3096 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-07-08T23:11:12.624+05:30  INFO 3096 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-07-08T23:11:12.900+05:30  WARN 3096 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-07-08T23:11:13.380+05:30  INFO 3096 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-07-08T23:11:13.393+05:30  INFO 3096 --- [           main] c.e.t.TestcontainersApplication          : Started TestcontainersApplication in 9.099 seconds (process running for 9.691)

Source Code: https://github.com/MMahendravarman/Springboot_Examples

Leave a Reply

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