Integration tests with Testcontainers and WireMock

Arun Chalise
3 min readFeb 27, 2024

--

While integration tests can be very valuable, they can only provide false confidence if they fail to replicate production settings accurately. In a typical application with numerous external dependencies such as databases, message brokers, and external APIs, conducting integration tests that cover the entire application flow is not easy and one common approach is to mock out these dependencies. Mocks cannot fully replace genuine integration, as they fail to provide meaningful data without considerable investment. They quickly become burdensome to maintain and, at most, offer a false safety.

Testcontainers are very powerful tools to create test environments using throwaway dependencies that resemble the target environment and can fill this gap. As an example, for an application that uses a Postgres database in production, you can spin up a throw away version of the same database that exists only for the duration of the tests. This immediately gives lot more confidence in our tests as the tests are executed against real dependencies with no contrived mock environment to maintain.

SpringBoot integrates very well with Testcontainers. And together with Wiremock which is also well supported by SpringBoot we can easily replace third party APIs to implement more reliable integration tests that are also easy to maintain since we no longer need to create and administer a parallel instance of mock environment from within the test code.

Here is a very minimal set up for integration tests using Testcontainers and Wiremock with SpringBoot. The application uses postgres database and exposes a REST endpoint which upon receiving user request makes a query to an external API, processes the result and saves into the database and finally returns response to the user, imitating a typical application flow.

The example controller makes call to an external API to retrieve a photo and which is added to the user submitted to data and saved as customer record in the database.

@RestController
class DemoController(private val webClient: WebClient, private val customerRepository: CustomerRepository) {
@GetMapping("/customer/{id}")
fun getCustomer(@PathVariable id: Long): Mono<Customer> {
val customer = customerRepository.findById(id)
return Mono.just(customer.get())
}

@PostMapping("/customer")
fun createCustomer(@RequestBody customerRecord: CustomerRecord): Mono<Customer> {
return webClient.get().uri("/photos/1").retrieve().bodyToMono(Photo::class.java).flatMap {
val customer = customerRepository.save(Customer(firstName = customerRecord.firstName,
lastName = customerRecord.lastName,
profileImageUrl = it.url))
Mono.just(customer)
}
}
}

The test code below uses testcontainer for postgres database and wiremock to stub the external API so that we can test the controller endpoint end to end using real databases and api servers.

@SpringBootTest
@AutoConfigureWebTestClient(timeout = "10000")
@AutoConfigureWireMock(stubs = ["classpath:/mappings/stub.json"])
@Testcontainers
class DemoControllerTest( @Autowired val client: WebTestClient, @Autowired val customerRepository: CustomerRepository) {
companion object {
private val db = PostgreSQLContainer("postgres")

@JvmStatic
@BeforeAll
fun startDBContainer() {
db.start()
}

@JvmStatic
@AfterAll
fun stopDBContainer() {
db.stop()
}

@JvmStatic
@DynamicPropertySource
fun registerDBContainer(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url", db::getJdbcUrl)
registry.add("spring.datasource.username", db::getUsername)
registry.add("spring.datasource.password", db::getPassword)
}
}

@LocalServerPort
private val port: Int? = null

@Test
fun `test creating a customer is successful with image retrieved from external api`() {
val customerRecord = CustomerRecord("testUser", "testLastName", "", "" )
client.post().uri("/customer")
.body(Mono.just(customerRecord), CustomerRecord::class.java)
.exchange()
.expectStatus().isOk
.expectBody()
.jsonPath("$.profileImageUrl")
.isNotEmpty
val customers = customerRepository.findAll()
assertTrue(customers.count() > 0)
}
}

The complete application is at https://github.com/achalise/testcontainer-wiremock-integration-tests

--

--

No responses yet