Skip to content

Commit 999210d

Browse files
feat: Use Testcontainers for controller tests (#54)
[Testcontainers](https://github.com/testcontainers) allow us to spin up a temporary Docker image during tests. For this project, we will spin up a temporary PostgreSQL database to run the controller tests against a real PostgreSQL database instead of an in-memory H2 database. There are differences between PostgreSQL and H2 databases so if you plan to use a PostgreSQL database in production, it also makes sense to mimic this by using a PostgreSQL database in the tests. H2 databases are useful for quickly testing and running the system locally, but it is better to use Testcontainers to run a real PostgreSQL database for the controller tests. Use Testcontainers for controller tests: - Add Gradle dependencies to `local.versions.toml` and `api/build.gradle.kts`: - `org.springframework.boot:spring-boot-testcontainers` - `org.testcontainers:postgresql` - `org.testcontainers:r2dbc` - Add `TestContainerConfig` which defines the PostgreSQL container and database settings. - Add annotations `@Import(TestContainerConfig::class)` and `@ActiveProfiles("postgres")` to `CustomerControllerTest` to ensure that a PostgreSQL database is used for the tests. - Add `application-postgres.yml` with connection settings for PostgreSQL. - Update `README.md`.
1 parent a451523 commit 999210d

File tree

6 files changed

+62
-1
lines changed

6 files changed

+62
-1
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ to view the Swagger documentation.
3939
- [H2database](https://github.com/h2database/h2database) - Provides an in-memory database for simple local testing
4040
- [R2DBC-H2](https://github.com/r2dbc/r2dbc-h2) - R2DBC driver for H2 database
4141
- [Liquibase](https://github.com/liquibase/liquibase) - Used to manage database schema changelogs
42+
- [Testcontainers](https://github.com/testcontainers) - Creates a temporary PostgreSQL database for tests
43+
44+
## Testing
45+
You can run the tests for this project using the following command:
46+
```
47+
./gradlew test
48+
```
49+
Please note that this project uses
50+
[Testcontainers](https://github.com/testcontainers)
51+
to create a temporary PostgreSQL database for tests. This requires
52+
a local Docker instance to be running when executing the tests.
4253

4354
## Gradle best practices for Kotlin
4455
[kotlinlang.org](https://kotlinlang.org/docs/gradle-best-practices.html)

apps/api/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ dependencies {
4242

4343
// Test dependencies
4444
testImplementation(local.springboot.starter.test)
45+
testImplementation(local.springboot.testcontainers)
46+
testImplementation(local.testcontainers.postgresql)
47+
testImplementation(local.testcontainers.r2dbc)
4548
testImplementation(local.kotlin.test.junit5)
4649
testRuntimeOnly(local.junit.platform.launcher)
4750
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
spring:
2+
r2dbc:
3+
url: r2dbc:postgresql://postgres:5432/sample-db
4+
username: postgres
5+
password: postgres
6+
liquibase:
7+
enabled: true
8+
# Note that we use jdbc for Liquibase and r2dbc for the application
9+
url: jdbc:postgresql://postgres:5432/sample-db
10+
user: postgres
11+
password: postgres

apps/api/src/test/kotlin/com/github/thorlauridsen/CustomerControllerTest.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,20 @@ import org.junit.jupiter.params.ParameterizedTest
1212
import org.junit.jupiter.params.provider.ValueSource
1313
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
1414
import org.springframework.boot.test.context.SpringBootTest
15+
import org.springframework.context.annotation.Import
16+
import org.springframework.test.context.ActiveProfiles
1517
import org.springframework.test.web.reactive.server.WebTestClient
1618

1719
/**
1820
* Test class for testing the CustomerController.
1921
* The test methods use [WebTestClient] to make requests to the controller endpoints.
2022
* [WebTestClient] has support for testing reactive web applications (Spring Boot Webflux).
23+
* A local Docker instance is required to run the tests as Testcontainers is used.
2124
*/
22-
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
25+
@ActiveProfiles("postgres")
2326
@AutoConfigureWebTestClient
27+
@Import(TestContainerConfig::class)
28+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
2429
class CustomerControllerTest(
2530
@Autowired private val client: WebTestClient
2631
) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.thorlauridsen
2+
3+
import org.springframework.boot.test.context.TestConfiguration
4+
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
5+
import org.springframework.context.annotation.Bean
6+
import org.testcontainers.containers.PostgreSQLContainer
7+
8+
@TestConfiguration(proxyBeanMethods = false)
9+
class TestContainerConfig {
10+
11+
/**
12+
* A [PostgreSQLContainer] bean to be used in tests.
13+
* This uses Testcontainers to spin up a temporary PostgreSQL instance in a Docker container.
14+
* The [ServiceConnection] annotation allows Spring Boot to automatically
15+
* configure the datasource properties based on the container settings.
16+
*/
17+
@Bean
18+
@ServiceConnection // lets Spring Boot wire spring.datasource.* automatically
19+
fun postgresContainer(): PostgreSQLContainer<*> {
20+
return PostgreSQLContainer("postgres:17")
21+
.withDatabaseName("sample-db")
22+
.withUsername("postgres")
23+
.withPassword("postgres")
24+
}
25+
}

gradle/local.versions.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ r2dbcPostgres = "1.0.7.RELEASE"
1111
springboot = "3.5.5"
1212
springDependencyPlugin = "1.1.7"
1313
springdoc = "2.8.11"
14+
testcontainers = "1.21.3"
1415

1516
[libraries]
1617
# H2 for in-memory database
@@ -45,6 +46,11 @@ springboot-starter = { module = "org.springframework.boot:spring-boot-starter",
4546
springboot-starter-r2dbc = { module = "org.springframework.boot:spring-boot-starter-data-r2dbc", version.ref = "springboot" }
4647
springboot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "springboot" }
4748
springboot-starter-webflux = { module = "org.springframework.boot:spring-boot-starter-webflux", version.ref = "springboot" }
49+
springboot-testcontainers = { module = "org.springframework.boot:spring-boot-testcontainers", version.ref = "springboot" }
50+
51+
# Testcontainers for running PostgreSQL in tests
52+
testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" }
53+
testcontainers-r2dbc = { module = "org.testcontainers:r2dbc", version.ref = "testcontainers" }
4854

4955
# Springdoc provides swagger docs with support for Spring Web MVC
5056
springdoc-openapi-starter-webflux = { module = "org.springdoc:springdoc-openapi-starter-webflux-ui", version.ref = "springdoc" }

0 commit comments

Comments
 (0)