Stub External APIs with WireMock and Spring Boot
Intro
It can be hard to test external APIs. Luckily many tools exist that make this process much easier. One of them is WireMock. It’s been in my toolkit for many years and I love how easy especially stubbing external APIs is.
This is an intro tutorial to stubbing with WireMock. There is much more to be discovered in future tutorials.
Let’s Code
I wanted to give the new RestClient a shot, so I’m using the latest Spring Boot version 3.2.0-M2
and bring in the spring-boot-starter-web
as well as the WireMock dependency com.github.tomakehurst:wiremock:3.0.1
.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "3.2.0-M2" id("io.spring.dependency-management") version "1.1.3" kotlin("jvm") version "1.9.10" kotlin("plugin.spring") version "1.9.10"} group = "dev.axgr"version = "0.0.1-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_17} repositories { mavenCentral() maven { url = uri("https://repo.spring.io/milestone") }} dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.springframework.boot:spring-boot-starter-web") testImplementation("com.github.tomakehurst:wiremock:3.0.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")} tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" jvmTarget = "17" }} tasks.withType<Test> { environment("spring.profiles.active", "test") useJUnitPlatform()}
The whole point of this tutorial is stubbing an external API and the once we are using today is The Rick and Morty API since it doesn’t require authentication, is free and has a nice documentation.
It supports characters, episodes and locations but for our example we will only use the episodes endpoint.
In order to use the new RestClient
, we need to add it to the Spring context. This can easily be done by requesting the RestClient.Builder
and building the client:
package dev.axgr import org.springframework.boot.autoconfigure.SpringBootApplicationimport org.springframework.boot.runApplicationimport org.springframework.context.annotation.Beanimport org.springframework.web.client.RestClient @SpringBootApplicationclass App { @Bean fun client(builder: RestClient.Builder) = builder.build() } fun main(args: Array<String>) { runApplication<App>(*args)}
If you have been using the WebClient
before, the API of the RestClient
should immediately feel familiar.
To access our external API, I create a component that requests the episodes from the API and returns them as a list of Episode
objects.
Since the API supports pagination, we can optionally pass in the page we are interested in.
package dev.axgr import org.springframework.boot.context.properties.ConfigurationPropertiesimport org.springframework.boot.context.properties.EnableConfigurationPropertiesimport org.springframework.stereotype.Componentimport org.springframework.web.client.RestClient @ConfigurationProperties("api")data class RickAndMortyProperties(val url: String) @Component@EnableConfigurationProperties(RickAndMortyProperties::class)class RickAndMorty(client: RestClient, props: RickAndMortyProperties) { private val http = client.mutate().baseUrl(props.url).build() fun episodes(page: Int = 1): List<Episode>? { return http .get() .uri { it .pathSegment("episode") .queryParam("page", page) .build() } .retrieve() .body(Episodes::class.java) ?.results }} data class Episode(val id: Int, val name: String, val episode: String)data class Episodes(val results: List<Episode>)
When we get to stubbing the data coming from the API, we need to point the component to the local WireMock server. We can do this by adding a new property to the RickAndMortyProperties
class and using it in the RestClient
builder. This allows us to override the base URL during our tests.
Let’s add the base URL to our application.properties
file.
api.url=https://rickandmortyapi.com/api
Time to verify that everything works as expected. Let’s write a test that requests the episodes from the API and asserts that the response is not null and contains at least one episode.
package dev.axgr import org.junit.jupiter.api.Assertionsimport org.junit.jupiter.api.Testimport org.junit.jupiter.api.assertThrowsimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.boot.test.context.SpringBootTestimport org.springframework.web.client.HttpClientErrorException @SpringBootTestclass AppTests { @Autowired private lateinit var api: RickAndMorty @Test fun episodes() { val episodes = api.episodes() Assertions.assertNotNull(episodes) Assertions.assertEquals(20, episodes!!.size) }}
This test is still using the real API and, unless the API is down, this test should pass. Going forward, I want to stub the request for the 2nd page of episodes and assert that the response contains the expected data.
We will copy the payload coming from the API and store it in a mapping file.
// src/test/resources/mappings/episode.json { "request": { "method": "GET", "urlPath": "/episode", "queryParameters": { "page": { "equalTo": "2" } } }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": { "info": { "count": 51, "pages": 3, "next": "https://rickandmortyapi.com/api/episode?page=3", "prev": "https://rickandmortyapi.com/api/episode?page=1" }, "results": [ { "id": 21, "name": "The Wedding Squanchers", "air_date": "October 4, 2015", "episode": "S02E10", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/23", "https://rickandmortyapi.com/api/character/47", "https://rickandmortyapi.com/api/character/55", "https://rickandmortyapi.com/api/character/75", "https://rickandmortyapi.com/api/character/102", "https://rickandmortyapi.com/api/character/130", "https://rickandmortyapi.com/api/character/131", "https://rickandmortyapi.com/api/character/133", "https://rickandmortyapi.com/api/character/194", "https://rickandmortyapi.com/api/character/199", "https://rickandmortyapi.com/api/character/203", "https://rickandmortyapi.com/api/character/240", "https://rickandmortyapi.com/api/character/244", "https://rickandmortyapi.com/api/character/256", "https://rickandmortyapi.com/api/character/261", "https://rickandmortyapi.com/api/character/308", "https://rickandmortyapi.com/api/character/309", "https://rickandmortyapi.com/api/character/311", "https://rickandmortyapi.com/api/character/331", "https://rickandmortyapi.com/api/character/344", "https://rickandmortyapi.com/api/character/358", "https://rickandmortyapi.com/api/character/362", "https://rickandmortyapi.com/api/character/379", "https://rickandmortyapi.com/api/character/454" ], "url": "https://rickandmortyapi.com/api/episode/21", "created": "2017-11-10T12:56:35.875Z" },
{ "id": 22, "name": "The Rickshank Rickdemption", "air_date": "April 1, 2017", "episode": "S03E01", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/21", "https://rickandmortyapi.com/api/character/22", "https://rickandmortyapi.com/api/character/38", "https://rickandmortyapi.com/api/character/42", "https://rickandmortyapi.com/api/character/47", "https://rickandmortyapi.com/api/character/48", "https://rickandmortyapi.com/api/character/57", "https://rickandmortyapi.com/api/character/69", "https://rickandmortyapi.com/api/character/71", "https://rickandmortyapi.com/api/character/86", "https://rickandmortyapi.com/api/character/94", "https://rickandmortyapi.com/api/character/95", "https://rickandmortyapi.com/api/character/103", "https://rickandmortyapi.com/api/character/150", "https://rickandmortyapi.com/api/character/152", "https://rickandmortyapi.com/api/character/175", "https://rickandmortyapi.com/api/character/200", "https://rickandmortyapi.com/api/character/215", "https://rickandmortyapi.com/api/character/231", "https://rickandmortyapi.com/api/character/240", "https://rickandmortyapi.com/api/character/274", "https://rickandmortyapi.com/api/character/285", "https://rickandmortyapi.com/api/character/286", "https://rickandmortyapi.com/api/character/294", "https://rickandmortyapi.com/api/character/295", "https://rickandmortyapi.com/api/character/330", "https://rickandmortyapi.com/api/character/338", "https://rickandmortyapi.com/api/character/344", "https://rickandmortyapi.com/api/character/378", "https://rickandmortyapi.com/api/character/380", "https://rickandmortyapi.com/api/character/385", "https://rickandmortyapi.com/api/character/389", "https://rickandmortyapi.com/api/character/461", "https://rickandmortyapi.com/api/character/462", "https://rickandmortyapi.com/api/character/463", "https://rickandmortyapi.com/api/character/464", "https://rickandmortyapi.com/api/character/465", "https://rickandmortyapi.com/api/character/466", "https://rickandmortyapi.com/api/character/592" ], "url": "https://rickandmortyapi.com/api/episode/22", "created": "2017-11-10T12:56:35.983Z" }, { "id": 23, "name": "Rickmancing the Stone", "air_date": "July 30, 2017", "episode": "S03E02", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/25", "https://rickandmortyapi.com/api/character/52", "https://rickandmortyapi.com/api/character/68", "https://rickandmortyapi.com/api/character/110", "https://rickandmortyapi.com/api/character/111", "https://rickandmortyapi.com/api/character/140", "https://rickandmortyapi.com/api/character/156", "https://rickandmortyapi.com/api/character/217", "https://rickandmortyapi.com/api/character/218", "https://rickandmortyapi.com/api/character/219", "https://rickandmortyapi.com/api/character/228", "https://rickandmortyapi.com/api/character/323", "https://rickandmortyapi.com/api/character/342" ], "url": "https://rickandmortyapi.com/api/episode/23", "created": "2017-11-10T12:56:36.100Z" }, { "id": 24, "name": "Pickle Rick", "air_date": "August 6, 2017", "episode": "S03E03", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/9", "https://rickandmortyapi.com/api/character/70", "https://rickandmortyapi.com/api/character/107", "https://rickandmortyapi.com/api/character/167", "https://rickandmortyapi.com/api/character/171", "https://rickandmortyapi.com/api/character/189", "https://rickandmortyapi.com/api/character/240", "https://rickandmortyapi.com/api/character/265", "https://rickandmortyapi.com/api/character/272", "https://rickandmortyapi.com/api/character/276", "https://rickandmortyapi.com/api/character/329" ], "url": "https://rickandmortyapi.com/api/episode/24", "created": "2017-11-10T12:56:36.206Z" }, { "id": 25, "name": "Vindicators 3: The Return of Worldender", "air_date": "August 13, 2017", "episode": "S03E04", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/10", "https://rickandmortyapi.com/api/character/23", "https://rickandmortyapi.com/api/character/35", "https://rickandmortyapi.com/api/character/60", "https://rickandmortyapi.com/api/character/81", "https://rickandmortyapi.com/api/character/88", "https://rickandmortyapi.com/api/character/93", "https://rickandmortyapi.com/api/character/104", "https://rickandmortyapi.com/api/character/125", "https://rickandmortyapi.com/api/character/181", "https://rickandmortyapi.com/api/character/198", "https://rickandmortyapi.com/api/character/208", "https://rickandmortyapi.com/api/character/216", "https://rickandmortyapi.com/api/character/226", "https://rickandmortyapi.com/api/character/251", "https://rickandmortyapi.com/api/character/252", "https://rickandmortyapi.com/api/character/282", "https://rickandmortyapi.com/api/character/309", "https://rickandmortyapi.com/api/character/311", "https://rickandmortyapi.com/api/character/333", "https://rickandmortyapi.com/api/character/340", "https://rickandmortyapi.com/api/character/362", "https://rickandmortyapi.com/api/character/375", "https://rickandmortyapi.com/api/character/382", "https://rickandmortyapi.com/api/character/395", "https://rickandmortyapi.com/api/character/435" ], "url": "https://rickandmortyapi.com/api/episode/25", "created": "2017-11-10T12:56:36.310Z" }, { "id": 26, "name": "The Whirly Dirly Conspiracy", "air_date": "August 20, 2017", "episode": "S03E05", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/23", "https://rickandmortyapi.com/api/character/47", "https://rickandmortyapi.com/api/character/115", "https://rickandmortyapi.com/api/character/137", "https://rickandmortyapi.com/api/character/142", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/204", "https://rickandmortyapi.com/api/character/296", "https://rickandmortyapi.com/api/character/297", "https://rickandmortyapi.com/api/character/319", "https://rickandmortyapi.com/api/character/320", "https://rickandmortyapi.com/api/character/365", "https://rickandmortyapi.com/api/character/369", "https://rickandmortyapi.com/api/character/467", "https://rickandmortyapi.com/api/character/468", "https://rickandmortyapi.com/api/character/469" ], "url": "https://rickandmortyapi.com/api/episode/26", "created": "2017-11-10T12:56:36.413Z" }, { "id": 27, "name": "Rest and Ricklaxation", "air_date": "August 27, 2017", "episode": "S03E06", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/6", "https://rickandmortyapi.com/api/character/124", "https://rickandmortyapi.com/api/character/170", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/181", "https://rickandmortyapi.com/api/character/227", "https://rickandmortyapi.com/api/character/240", "https://rickandmortyapi.com/api/character/246", "https://rickandmortyapi.com/api/character/272", "https://rickandmortyapi.com/api/character/332", "https://rickandmortyapi.com/api/character/360", "https://rickandmortyapi.com/api/character/361", "https://rickandmortyapi.com/api/character/365", "https://rickandmortyapi.com/api/character/470", "https://rickandmortyapi.com/api/character/471" ], "url": "https://rickandmortyapi.com/api/episode/27", "created": "2017-11-10T12:56:36.515Z" }, { "id": 28, "name": "The Ricklantis Mixup", "air_date": "September 10, 2017", "episode": "S03E07", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/8", "https://rickandmortyapi.com/api/character/18", "https://rickandmortyapi.com/api/character/22", "https://rickandmortyapi.com/api/character/27", "https://rickandmortyapi.com/api/character/43", "https://rickandmortyapi.com/api/character/44", "https://rickandmortyapi.com/api/character/48", "https://rickandmortyapi.com/api/character/56", "https://rickandmortyapi.com/api/character/61", "https://rickandmortyapi.com/api/character/72", "https://rickandmortyapi.com/api/character/73", "https://rickandmortyapi.com/api/character/74", "https://rickandmortyapi.com/api/character/78", "https://rickandmortyapi.com/api/character/85", "https://rickandmortyapi.com/api/character/86", "https://rickandmortyapi.com/api/character/118", "https://rickandmortyapi.com/api/character/123", "https://rickandmortyapi.com/api/character/135", "https://rickandmortyapi.com/api/character/143", "https://rickandmortyapi.com/api/character/165", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/187", "https://rickandmortyapi.com/api/character/206", "https://rickandmortyapi.com/api/character/220", "https://rickandmortyapi.com/api/character/229", "https://rickandmortyapi.com/api/character/233", "https://rickandmortyapi.com/api/character/235", "https://rickandmortyapi.com/api/character/267", "https://rickandmortyapi.com/api/character/278", "https://rickandmortyapi.com/api/character/281", "https://rickandmortyapi.com/api/character/283", "https://rickandmortyapi.com/api/character/284", "https://rickandmortyapi.com/api/character/287", "https://rickandmortyapi.com/api/character/288", "https://rickandmortyapi.com/api/character/289", "https://rickandmortyapi.com/api/character/291", "https://rickandmortyapi.com/api/character/292", "https://rickandmortyapi.com/api/character/322", "https://rickandmortyapi.com/api/character/325", "https://rickandmortyapi.com/api/character/328", "https://rickandmortyapi.com/api/character/345", "https://rickandmortyapi.com/api/character/366", "https://rickandmortyapi.com/api/character/367", "https://rickandmortyapi.com/api/character/392", "https://rickandmortyapi.com/api/character/472", "https://rickandmortyapi.com/api/character/473", "https://rickandmortyapi.com/api/character/474", "https://rickandmortyapi.com/api/character/475", "https://rickandmortyapi.com/api/character/476", "https://rickandmortyapi.com/api/character/477", "https://rickandmortyapi.com/api/character/478", "https://rickandmortyapi.com/api/character/479", "https://rickandmortyapi.com/api/character/480", "https://rickandmortyapi.com/api/character/481", "https://rickandmortyapi.com/api/character/482", "https://rickandmortyapi.com/api/character/483", "https://rickandmortyapi.com/api/character/484", "https://rickandmortyapi.com/api/character/485", "https://rickandmortyapi.com/api/character/486", "https://rickandmortyapi.com/api/character/487", "https://rickandmortyapi.com/api/character/488", "https://rickandmortyapi.com/api/character/489" ], "url": "https://rickandmortyapi.com/api/episode/28", "created": "2017-11-10T12:56:36.618Z" }, { "id": 29, "name": "Morty's Mind Blowers", "air_date": "September 17, 2017", "episode": "S03E08", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/33", "https://rickandmortyapi.com/api/character/67", "https://rickandmortyapi.com/api/character/147", "https://rickandmortyapi.com/api/character/149", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/242", "https://rickandmortyapi.com/api/character/244", "https://rickandmortyapi.com/api/character/251", "https://rickandmortyapi.com/api/character/272", "https://rickandmortyapi.com/api/character/329", "https://rickandmortyapi.com/api/character/368", "https://rickandmortyapi.com/api/character/377", "https://rickandmortyapi.com/api/character/390", "https://rickandmortyapi.com/api/character/490", "https://rickandmortyapi.com/api/character/491" ], "url": "https://rickandmortyapi.com/api/episode/29", "created": "2017-11-10T12:56:36.726Z" }, { "id": 30, "name": "The ABC's of Beth", "air_date": "September 24, 2017", "episode": "S03E09", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/58", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/185", "https://rickandmortyapi.com/api/character/190", "https://rickandmortyapi.com/api/character/240", "https://rickandmortyapi.com/api/character/244", "https://rickandmortyapi.com/api/character/245", "https://rickandmortyapi.com/api/character/249", "https://rickandmortyapi.com/api/character/329", "https://rickandmortyapi.com/api/character/350", "https://rickandmortyapi.com/api/character/357", "https://rickandmortyapi.com/api/character/363", "https://rickandmortyapi.com/api/character/492" ], "url": "https://rickandmortyapi.com/api/episode/30", "created": "2017-11-10T12:56:36.828Z" }, { "id": 31, "name": "The Rickchurian Mortydate", "air_date": "October 1, 2017", "episode": "S03E10", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/13", "https://rickandmortyapi.com/api/character/30", "https://rickandmortyapi.com/api/character/166", "https://rickandmortyapi.com/api/character/244", "https://rickandmortyapi.com/api/character/247", "https://rickandmortyapi.com/api/character/269", "https://rickandmortyapi.com/api/character/335", "https://rickandmortyapi.com/api/character/347", "https://rickandmortyapi.com/api/character/493" ], "url": "https://rickandmortyapi.com/api/episode/31", "created": "2017-11-10T12:56:36.929Z" }, { "id": 32, "name": "Edge of Tomorty: Rick, Die, Rickpeat", "air_date": "November 10, 2019", "episode": "S04E01", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/242", "https://rickandmortyapi.com/api/character/494", "https://rickandmortyapi.com/api/character/495", "https://rickandmortyapi.com/api/character/496", "https://rickandmortyapi.com/api/character/497", "https://rickandmortyapi.com/api/character/498", "https://rickandmortyapi.com/api/character/499", "https://rickandmortyapi.com/api/character/500", "https://rickandmortyapi.com/api/character/501", "https://rickandmortyapi.com/api/character/502", "https://rickandmortyapi.com/api/character/503", "https://rickandmortyapi.com/api/character/504", "https://rickandmortyapi.com/api/character/505", "https://rickandmortyapi.com/api/character/506", "https://rickandmortyapi.com/api/character/507", "https://rickandmortyapi.com/api/character/508", "https://rickandmortyapi.com/api/character/509", "https://rickandmortyapi.com/api/character/510", "https://rickandmortyapi.com/api/character/511", "https://rickandmortyapi.com/api/character/512", "https://rickandmortyapi.com/api/character/513", "https://rickandmortyapi.com/api/character/514", "https://rickandmortyapi.com/api/character/515", "https://rickandmortyapi.com/api/character/516", "https://rickandmortyapi.com/api/character/517", "https://rickandmortyapi.com/api/character/518", "https://rickandmortyapi.com/api/character/519", "https://rickandmortyapi.com/api/character/520", "https://rickandmortyapi.com/api/character/521", "https://rickandmortyapi.com/api/character/522", "https://rickandmortyapi.com/api/character/523", "https://rickandmortyapi.com/api/character/524" ], "url": "https://rickandmortyapi.com/api/episode/32", "created": "2020-04-30T06:52:04.495Z" }, { "id": 33, "name": "The Old Man and the Seat", "air_date": "November 17, 2019", "episode": "S04E02", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/525", "https://rickandmortyapi.com/api/character/526", "https://rickandmortyapi.com/api/character/527", "https://rickandmortyapi.com/api/character/528", "https://rickandmortyapi.com/api/character/529", "https://rickandmortyapi.com/api/character/530", "https://rickandmortyapi.com/api/character/531", "https://rickandmortyapi.com/api/character/532", "https://rickandmortyapi.com/api/character/533", "https://rickandmortyapi.com/api/character/534", "https://rickandmortyapi.com/api/character/535", "https://rickandmortyapi.com/api/character/536", "https://rickandmortyapi.com/api/character/537", "https://rickandmortyapi.com/api/character/538", "https://rickandmortyapi.com/api/character/539", "https://rickandmortyapi.com/api/character/540", "https://rickandmortyapi.com/api/character/541", "https://rickandmortyapi.com/api/character/542", "https://rickandmortyapi.com/api/character/543" ], "url": "https://rickandmortyapi.com/api/episode/33", "created": "2020-04-30T06:52:04.498Z" }, { "id": 34, "name": "One Crew Over the Crewcoo's Morty", "air_date": "November 24, 2019", "episode": "S04E03", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/244", "https://rickandmortyapi.com/api/character/544", "https://rickandmortyapi.com/api/character/545", "https://rickandmortyapi.com/api/character/546", "https://rickandmortyapi.com/api/character/547", "https://rickandmortyapi.com/api/character/548", "https://rickandmortyapi.com/api/character/549", "https://rickandmortyapi.com/api/character/550", "https://rickandmortyapi.com/api/character/551", "https://rickandmortyapi.com/api/character/552", "https://rickandmortyapi.com/api/character/553", "https://rickandmortyapi.com/api/character/554", "https://rickandmortyapi.com/api/character/555", "https://rickandmortyapi.com/api/character/556", "https://rickandmortyapi.com/api/character/557", "https://rickandmortyapi.com/api/character/558", "https://rickandmortyapi.com/api/character/559", "https://rickandmortyapi.com/api/character/560", "https://rickandmortyapi.com/api/character/561" ], "url": "https://rickandmortyapi.com/api/episode/34", "created": "2020-04-30T06:52:04.498Z" }, { "id": 35, "name": "Claw and Hoarder: Special Ricktim's Morty", "air_date": "December 8, 2019", "episode": "S04E04", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/562", "https://rickandmortyapi.com/api/character/563", "https://rickandmortyapi.com/api/character/564", "https://rickandmortyapi.com/api/character/565", "https://rickandmortyapi.com/api/character/566", "https://rickandmortyapi.com/api/character/567", "https://rickandmortyapi.com/api/character/568", "https://rickandmortyapi.com/api/character/569", "https://rickandmortyapi.com/api/character/570" ], "url": "https://rickandmortyapi.com/api/episode/35", "created": "2020-04-30T06:52:04.498Z" }, { "id": 36, "name": "Rattlestar Ricklactica", "air_date": "December 15, 2019", "episode": "S04E05", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/251", "https://rickandmortyapi.com/api/character/313", "https://rickandmortyapi.com/api/character/365", "https://rickandmortyapi.com/api/character/571", "https://rickandmortyapi.com/api/character/572", "https://rickandmortyapi.com/api/character/573", "https://rickandmortyapi.com/api/character/574", "https://rickandmortyapi.com/api/character/575", "https://rickandmortyapi.com/api/character/576", "https://rickandmortyapi.com/api/character/577", "https://rickandmortyapi.com/api/character/578", "https://rickandmortyapi.com/api/character/579", "https://rickandmortyapi.com/api/character/580", "https://rickandmortyapi.com/api/character/581", "https://rickandmortyapi.com/api/character/582", "https://rickandmortyapi.com/api/character/583", "https://rickandmortyapi.com/api/character/584", "https://rickandmortyapi.com/api/character/585", "https://rickandmortyapi.com/api/character/586", "https://rickandmortyapi.com/api/character/587", "https://rickandmortyapi.com/api/character/588", "https://rickandmortyapi.com/api/character/589", "https://rickandmortyapi.com/api/character/590", "https://rickandmortyapi.com/api/character/591" ], "url": "https://rickandmortyapi.com/api/episode/36", "created": "2020-04-30T06:52:04.499Z" }, { "id": 37, "name": "Never Ricking Morty", "air_date": "May 3, 2020", "episode": "S04E06", "characters": [ "https://rickandmortyapi.com/api/character/593", "https://rickandmortyapi.com/api/character/594", "https://rickandmortyapi.com/api/character/595", "https://rickandmortyapi.com/api/character/596", "https://rickandmortyapi.com/api/character/597", "https://rickandmortyapi.com/api/character/598", "https://rickandmortyapi.com/api/character/599", "https://rickandmortyapi.com/api/character/600", "https://rickandmortyapi.com/api/character/601", "https://rickandmortyapi.com/api/character/602", "https://rickandmortyapi.com/api/character/603", "https://rickandmortyapi.com/api/character/604", "https://rickandmortyapi.com/api/character/605", "https://rickandmortyapi.com/api/character/606", "https://rickandmortyapi.com/api/character/607", "https://rickandmortyapi.com/api/character/608", "https://rickandmortyapi.com/api/character/609", "https://rickandmortyapi.com/api/character/610", "https://rickandmortyapi.com/api/character/611", "https://rickandmortyapi.com/api/character/612", "https://rickandmortyapi.com/api/character/613", "https://rickandmortyapi.com/api/character/614", "https://rickandmortyapi.com/api/character/615", "https://rickandmortyapi.com/api/character/616", "https://rickandmortyapi.com/api/character/617", "https://rickandmortyapi.com/api/character/618", "https://rickandmortyapi.com/api/character/619", "https://rickandmortyapi.com/api/character/620", "https://rickandmortyapi.com/api/character/621", "https://rickandmortyapi.com/api/character/622", "https://rickandmortyapi.com/api/character/623", "https://rickandmortyapi.com/api/character/624", "https://rickandmortyapi.com/api/character/625", "https://rickandmortyapi.com/api/character/626", "https://rickandmortyapi.com/api/character/627", "https://rickandmortyapi.com/api/character/628", "https://rickandmortyapi.com/api/character/629", "https://rickandmortyapi.com/api/character/630", "https://rickandmortyapi.com/api/character/631", "https://rickandmortyapi.com/api/character/632", "https://rickandmortyapi.com/api/character/633", "https://rickandmortyapi.com/api/character/634", "https://rickandmortyapi.com/api/character/635", "https://rickandmortyapi.com/api/character/636", "https://rickandmortyapi.com/api/character/637", "https://rickandmortyapi.com/api/character/638", "https://rickandmortyapi.com/api/character/639", "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2" ], "url": "https://rickandmortyapi.com/api/episode/37", "created": "2020-08-06T05:44:21.422Z" }, { "id": 38, "name": "Promortyus", "air_date": "May 10, 2020", "episode": "S04E07", "characters": [ "https://rickandmortyapi.com/api/character/640", "https://rickandmortyapi.com/api/character/641", "https://rickandmortyapi.com/api/character/642", "https://rickandmortyapi.com/api/character/643", "https://rickandmortyapi.com/api/character/644", "https://rickandmortyapi.com/api/character/645", "https://rickandmortyapi.com/api/character/646", "https://rickandmortyapi.com/api/character/647", "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/365" ], "url": "https://rickandmortyapi.com/api/episode/38", "created": "2020-08-06T05:49:40.563Z" }, { "id": 39, "name": "The Vat of Acid Episode", "air_date": "May 17, 2020", "episode": "S04E08", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/240", "https://rickandmortyapi.com/api/character/180", "https://rickandmortyapi.com/api/character/648", "https://rickandmortyapi.com/api/character/649", "https://rickandmortyapi.com/api/character/650", "https://rickandmortyapi.com/api/character/651", "https://rickandmortyapi.com/api/character/652", "https://rickandmortyapi.com/api/character/653", "https://rickandmortyapi.com/api/character/654", "https://rickandmortyapi.com/api/character/655", "https://rickandmortyapi.com/api/character/656", "https://rickandmortyapi.com/api/character/657", "https://rickandmortyapi.com/api/character/658", "https://rickandmortyapi.com/api/character/659", "https://rickandmortyapi.com/api/character/660", "https://rickandmortyapi.com/api/character/661" ], "url": "https://rickandmortyapi.com/api/episode/39", "created": "2020-08-06T05:51:07.419Z" }, { "id": 40, "name": "Childrick of Mort", "air_date": "May 24, 2020", "episode": "S04E09", "characters": [ "https://rickandmortyapi.com/api/character/1", "https://rickandmortyapi.com/api/character/2", "https://rickandmortyapi.com/api/character/3", "https://rickandmortyapi.com/api/character/4", "https://rickandmortyapi.com/api/character/5", "https://rickandmortyapi.com/api/character/662", "https://rickandmortyapi.com/api/character/663", "https://rickandmortyapi.com/api/character/664", "https://rickandmortyapi.com/api/character/665", "https://rickandmortyapi.com/api/character/666" ], "url": "https://rickandmortyapi.com/api/episode/40", "created": "2020-08-06T05:51:25.458Z" } ] } }}
Stubbing in WireMock is very powerful and we are only using a subset of the available features. We could have written everything in this mapping file ourselves with code, but I preffer the mapping files.
The jsonBody
contains the raw response from the API that we want to stub and the body is part of the response
we are describing.
We also expect it to have the HTTP 200 status code and the Content-Type header set to application/json
.
There is also the request
object. This defines what requests should be matched by this mapping.
In this example we match all requests that match this path and query parameter: /episode?page=2
- requesting the first, third or any other page will not match this mapping.
But WireMock isn’t activated just yet, we have to change a few things first. Create a new properties file that is being used for the test.
# src/test/resources/application-test.properties api.url=http://localhost:8081
This points our Spring component to perform requests against this local URL where the WireMock server will be listening. However, this properties file is only considered for the profile “test” so we have to activate that during tests as follows:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "3.2.0-M2" id("io.spring.dependency-management") version "1.1.3" kotlin("jvm") version "1.9.10" kotlin("plugin.spring") version "1.9.10"} group = "dev.axgr"version = "0.0.1-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_17} repositories { mavenCentral() maven { url = uri("https://repo.spring.io/milestone") }} dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("com.github.tomakehurst:wiremock:3.0.1")} tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" jvmTarget = "17" }} tasks.withType<Test> { environment("spring.profiles.active", "test") useJUnitPlatform()}
If we run the test now, it will fail because we haven’t started the WireMock server yet. We need to adjust the test file as follows.
package dev.axgr import com.github.tomakehurst.wiremock.junit5.WireMockTestimport org.junit.jupiter.api.Assertionsimport org.junit.jupiter.api.Testimport org.junit.jupiter.api.assertThrowsimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.boot.test.context.SpringBootTestimport org.springframework.web.client.HttpClientErrorException @SpringBootTest@WireMockTest(httpPort = 8081)class AppTests { @Autowired private lateinit var api: RickAndMorty @Test fun episodes() { assertThrows<HttpClientErrorException.NotFound> { api.episodes(1) } val episodes = api.episodes(2) Assertions.assertNotNull(episodes) Assertions.assertEquals(20, episodes!!.size) Assertions.assertEquals("The Wedding Squanchers", episodes.first().name) }}
This brings up the WireMock server at port 8081
which is waiting patiently for requests matching the given mapping.
We verify that requesting a page that is not stubbed will result in a 404
response.
The remaining assertions should happily pass.