Skip to content
thr0n's Blog 🐧
GitHubLinkedInMastodon

Keep your tests green: Mock external API calls using WireMock

Kotlin, Spring Boot, Testing6 min read

When your software project is growing, chances are getting higher that you might want to (or have to 😉) send API calls to an external system you do not control but have to communicate with in order to use a service they provide.

Depending on your system's domain or your specific use case there are seemingly endless external services you could use. You may want to:

  • ensure that the visitor on your website isn't a bot (using "Google reCAPTCHA" or the like)
  • ask a geo information service for the latitude and longitude of a given address
  • add an item to a shopping cart service or many others

Calling an external API during execution of your application is perfectly fine, because otherwise you won't be able to use their service at all. But since interacting with an external system always bears the risk of failures (downtimes, breaking changes, etc.) you will probably take actions to make your app resilient to failing API calls.

Speaking of external systems and failures: If you added some tests to your project (and i hope you did!) you do not want to send real API calls to any external system during your tests. Calling real APIs during test execution is a bad practice. In general you don't want your tests to break if an external service is unavailable or behaving strange. Moreover,I assume that you don't want to test an external API, but rather focus on your application.

But how can we test an application that uses external APIs without sending request to an external system? I'm glad you ask!

I used WireMock in several projects to achieve this goal. WireMock describes itself as:

[…] a simulator for HTTP-based APIs. […] It enables you to stay productive when an API you depend on doesn't exist or isn't complete. It supports testing of edge cases and failure modes that the real API won't reliably produce.

To me this sounds like a perfect match! If you now can't wait to use WireMock in your project you should definitely read on. I'll show you how to add WireMock to a Spring Boot/Kotlin application and how to test a Spring @Service that sends a GET request to an external API.


HandsOn: Use WireMock with Kotlin, Spring Boot, and JUnit 5

For this how-to i created a new project using Spring Initializr. It's up to you to decide if you start from scratch or add WireMock to your already existing project. In both cases you have to add Spring Web, Spring Configuration Processer and of course WireMock to your Gradle build file to get started.

Afterwards we prepare the application.yml file to provide information about the external host. To keep this how-to as simple as possible we only add the hostname of the external service we want to communicate with. In this case it's just "http://example.org":

api:
product_availablity:
host: "http://example.org"

The hostname will be accessed using a @Configuration bean. This is necessary since later we will override this bean in our test configuration to set the hostname to a different value:

package com.medium.thr0n.wiremockdemo.configuration
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties("api.productavailabilty")
class ProductAvailabilityProperties {
lateinit var host: String
}

To call the external service we finally wire the Configuration bean into a simple demo service, read the current hostname and send the GET request using a RestTemplate:

package com.medium.thr0n.wiremockdemo.service
import com.medium.thr0n.wiremockdemo.configuration.ProductAvailabilityProperties
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.client.HttpClientErrorException
import org.springframework.web.client.RestTemplate
import org.springframework.web.server.ResponseStatusException
@Service
class ProductAvailabilityService(val properties: ProductAvailabilityProperties) {
val restTemplate = RestTemplate()
fun isAvailable(articleId: String): String? {
try {
val response = restTemplate.getForEntity(
properties.host + "/external-service/product-availability/" + articleId,
String::class.java)
return response.body
} catch (e: HttpClientErrorException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
}
}

That was the implementation part. Simple, right? So, let's add a test!


As said before we have to replace the configuration bean that stores the hostname of the external services, so we must allow bean definition overriding during test execution. To do so, we add a new application.yml file to src/test/resources/ and add the following configuration:

spring:
main:
allow-bean-definition-overriding: true

Now it's time to configure the WireMock server. First of all we tell WireMock to listen on a dynamic port. So hopefully we won't get into any trouble when our WireMock is executed by a CI/CD pipeline. To complete the WireMock setup we override the hostname of the external API to the baseUrl of the WireMock server. So from now on the ProductAvailabilityService will send all request to our WireMock instead of example.org.

To receive valid responses from WireMock we add two simple stubs. The first stub will just respond with a HTTP status 200 if the given product id is equal to 1. If the product id is equal to 0 HTTP status 404 will be returned.

Now we only have to start the WireMock server during the extension initialzitation. Ah, and don't forget to stop the WireMock inside the AfterAll function!

package com.medium.thr0n.wiremockdemo.junit
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import com.medium.thr0n.wiremockdemo.configuration.ProductAvailabilityProperties
import org.junit.jupiter.api.extension.AfterAllCallback
import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Primary
private val productAvailabilityWiremockServer =
WireMockServer(WireMockConfiguration.options().dynamicPort())
@TestConfiguration
class ProductAvailabilityWiremockExtension : BeforeAllCallback, AfterAllCallback {
val endpoint = "/external-service/product-availability"
init { productAvailabilityWiremockServer.start() }
@Bean
@Primary
fun productAvailabilityProperties(): ProductAvailabilityProperties {
val props = ProductAvailabilityProperties()
props.host = productAvailabilityWiremockServer.baseUrl()
return props
}
override fun beforeAll(context: ExtensionContext?) {
productAvailabilityWiremockServer.stubFor(
get(urlPathEqualTo("$endpoint/1"))
.willReturn(ok())
)
productAvailabilityWiremockServer.stubFor(
get(urlPathEqualTo("$endpoint/0"))
.willReturn(notFound())
)
}
override fun afterAll(context: ExtensionContext?) {
productAvailabilityWiremockServer.stop()
}
}

If you think that this is just a very simple stub that doesn't that much, you're absolutely right! But for this how-to it's completely sufficient. Would you like to dive deeper into stubbing? Check out the WireMock docs, they have lots of examples: http://wiremock.org/docs/stubbing/

At the end it's time to add a @SpringBootTest. To use our WireMock we import the above extension class and extend the test with the same class. We use a TestRestTemplate to send GET request to a rest endpoint. The called endpoint will invoke the ProductAvailabilityService which in turn sends a GET request to the external API. But wait! Since the request is sent during test execution we won't ask example.org for the availability of a product. Instead WireMock will answer the request using the stub we defined above. Thank you, WireMock! 🙏

package com.medium.thr0n.wiremockdemo.controllers
import com.medium.thr0n.wiremockdemo.junit.ProductAvailabilityWiremockExtension
import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.web.server.LocalServerPort
import org.springframework.context.annotation.Import
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(ProductAvailabilityWiremockExtension::class)
@ExtendWith(ProductAvailabilityWiremockExtension::class)
internal class ProductAvailabilityControllerIntTest(
@LocalServerPort val port: Int
) {
val endpoint = "http://localhost:$port/api/product-availability"
val restTemplate = TestRestTemplate()
@Test
internal fun `Should return HTTP status OK for a valid product id`() {
val response = restTemplate.exchange(
"$endpoint/1",
HttpMethod.GET,
null,
String::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
@Test
internal fun `Should return HTTP status NOT FOUND for an invalid product id`() {
val response = restTemplate.exchange(
"$endpoint/0",
HttpMethod.GET,
null,
String::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.NOT_FOUND)
}
}

It's a wrap! If you like to see or run this small WireMock example on your machine feel free to checkout the repo at Github: https://github.com/thr0n/medium-wiremock-demo

Do you have any questions regarding the how-to? Are you already using WireMock in your project? Or do you just want to say how great WireMock simplifies testing of applications that are interactiv with external APIs? I'm looking forward to reading your comment!

Happy mocking!

Further reading

If you are interested in software reslience there are great articles out there. Here’s a short list just to name a few:

© 2024 by thr0n's Blog 🐧. All rights reserved.
Theme by LekoArts