Stub External APIs with WireMock and Spring Boot

Posted on Sep 6, 2023 in
Reading time: 10 minutes

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.

🍿 Watch on YouTube or get the code from GitHub

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.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.web.client.RestClient
 
@SpringBootApplication
class 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.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.stereotype.Component
import 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.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.web.client.HttpClientErrorException
 
@SpringBootTest
class 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.WireMockTest
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import 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.

The Dead Letter Queue

Love what you're seeing? By subscribing to my newsletter, not only will you be the first to know about fresh tutorials and videos, but you'll also unlock:

Subscribe now and become a part of our growing tech tribe!