Testcontainers in Java Spring Boot 

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.(1)

In this blog post , we will utilize Testcontainers for testing a Java Spring Boot application integrated with a Postgres database.

To run Testcontainers-based tests, you need a Docker-API compatible container runtime, such as using Testcontainers Cloud or installing Docker locally. These Docker environments are automatically detected and used by Testcontainers without any additional configuration being necessary.(2)

Let’s start.

1. Create Spring boot project.

2. build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.boot:spring-boot-testcontainers'
    testImplementation 'org.testcontainers:junit-jupiter'
    testImplementation 'org.testcontainers:postgresql'
}

3. Create Entity

@Entity
@Table(name = "books")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String name;
    @Column(nullable = false)
    private String author;

    public Book() {
    }

    public Book(Long id, String name, String author) {
        this.id = id;
        this.name = name;
        this.author = author;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

4. Repository

@Repository
interface BookRepository extends JpaRepository<Book, Long> {
}

5. Controller

@RestController
@RequestMapping("api/v1/books")
class BookController {

    private final BookRepository bookRepository;


    public BookController(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @GetMapping("/find-all")
    ResponseEntity<List<Book>> findAll() {
        return ResponseEntity.ok(bookRepository.findAll());
    }
}

6. Create a schema.sql file with the following content under the src/main/resources directory.

CREATE TABLE IF NOT EXISTS books
(
    id serial primary key,
    name varchar(255) not null,
    author varchar(255) not null
);

7. application.yaml

spring:
  sql:
    init:
      mode: always
#  datasource:
#    url: jdbc:tc:postgresql:16:///booksdb

There are two ways for database containers launch:

1. application.yaml: jdbc:tc:postgresql:version:///databasename : can be used without requiring modification to your application code.

2. @DynamicPropertySource

BUT with Spring Boot 3.1;

New @ServiceConnection annotation instead of @DynamicPropertySource can be used on the container instance fields of our tests.

8. BookControllerTest

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

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:16");

    @Autowired
    BookRepository bookRepository;

    @Autowired
    TestRestTemplate testRestTemplate;

    @BeforeEach
    void setUp() {
        List<Book> books = List.of(
                new Book(1L, " The Hobbit", "J. R. R. Tolkien"),
                new Book(2L, "The Lord of the Rings", "J. R. R. Tolkien")
        );
        bookRepository.saveAll(books);
    }

    @Test
    @DisplayName("find all books")
    void shouldFindAllBooks() {

        ResponseEntity<List<Book>> response = testRestTemplate.exchange(
                "/api/v1/books/find-all",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<>() {
                }
        );

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertNotNull(response.getBody());
        assertEquals(2, response.getBody().size());
    }
}

Execute the HTTP method to the given URI template, writing the given request entity to the request, and returns the response as ResponseEntity. The given ParameterizedTypeReference is used to pass generic type information.(3)

Parameters:

url – the URL

method – the HTTP method (GET, POST, etc.)

requestEntity – the entity (headers and/or body) to write to the request, may be null

responseType – the type of the return value

You can access the source code by following this link.

References

https://testcontainers.com (1)
https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1
https://java.testcontainers.org/modules/databases/jdbc
https://java.testcontainers.org/supported_docker_environment(2)
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/web/client/TestRestTemplate.html(3)
https://testcontainers.com/modules/postgresql
https://github.com/testcontainers/tc-guide-testing-spring-boot-rest-api
https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/html/boot-features-testing.html
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.using-basic-sql-scripts