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/apiTime 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:8081This 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.