Wiremock-testing - WireMock - A great library to mock APIs in your tests and supports Junit5

Related tags

App testing wiremock
Overview

CI

WireMock Testing

WireMock is a great library to mock APIs in your tests and supports Junit5 with two modes:

  • Declarative with @WireMockTest
  • Programmatic with WireMockExtension

And WireMock also has an official Docker image!

But "talk is cheap, show me the code ..." 😮

Ok so let's implement first the scenario with @WireMockTest:

WireMockTest

And later the one with WireMock's official Docker image:

WireMockDockerTest

BarClient

BarClient interface

interface BarClient {

  fun call(name: String): String
}

BarKtorClient test

I will use a Ktor client for no other reason that I need an Http client and this seems interesting, as we are using Kotlin.

So a simple @WireMockTest for the BarKtorClient looks like:

@WireMockTest
class BarKtorClientShould {

 private val name = "Sue"
 
 @Test
 fun `call bar api`(wm: WireMockRuntimeInfo) {
  stubFor(
   get(urlPathMatching("/bar/$name"))
   .willReturn(ok().withBody("Hello $name I am Bar!"))
  )

  assertThat(
    BarKtorClient(wm.httpBaseUrl).call(name)
  ).isEqualTo("Hello $name I am Bar!")
 }

 @Test
 fun `handle bar api server error`(wm: WireMockRuntimeInfo) {
  stubFor(
   get(urlPathMatching("/bar/.+"))
   .willReturn(serverError())
  )

  assertThat(BarKtorClient(wm.httpBaseUrl).call(name))
   .startsWith("Bar api error: Server error")
 }
}

BarKtorClient implementation

In order to make the test pass 🟩 we can implement the BarKtorClient this way:

class BarKtorClient(private val url: String) : BarClient {

 private val client = HttpClient(CIO)

 override fun call(name: String): String = runBlocking {
  try {
   client.get("$url/bar/$name")
  } catch (e: Exception) {
   "Bar api error: ${e.message}"
  }
 }
}

FooClient

FooClient interface

interface FooClient {
  
  fun call(name: String): String
}

FooKtorClient test

For this test I want to use WireMock's response templating feature, so I will register a WireMockExtension instead of using @WireMockTest:

@TestInstance(PER_CLASS)
class FooKtorClientShould {
  
 private val name = "Joe"

 @RegisterExtension
 val wm: WireMockExtension = WireMockExtension.newInstance()
  .options(wireMockConfig()
    .extensions(ResponseTemplateTransformer(true))
  )
  .configureStaticDsl(true)
  .build()

 @Test
 fun `call foo api`() {
  stubFor(
   get(urlPathEqualTo("/foo"))
   .withQueryParam("name", matching(".+"))
   .willReturn(ok().withBody("Hello {{request.query.name}} I am Foo!"))
  )

  assertThat(FooKtorClient(wm.baseUrl()).call(name))
   .isEqualTo("Hello $name I am Foo!")
 }

 @Test
 fun `handle foo api server error`() {
  stubFor(
   get(urlPathEqualTo("/foo"))
   .willReturn(WireMock.serverError())
  )

  assertThat(FooKtorClient(wm.baseUrl()).call(name))
   .startsWith("Foo api error: Server error")
 }
}

Note that:

  • Instead of having a fixed response, with WireMock's response templating we can insert in the response values from the request. In this case the query parameter name.
  • @TestInstance(PER_CLASS) makes JUnit5 create a single instance of FooKtorClientShould to be used by both tests so the WireMockExtension is registered only once. By default JUnit5 would create one instance for each test (see Test Instance Lifecycle).
  • configureStaticDsl(true) makes it possible to use the static DSL, that is using stubFor(...) staticly instead of wm.stubFor(...).

FooKtorClient implementation

Same as before in order to make the test pass 🟩 we can implement the FooKtorClient this way:

class FooKtorClient(private val url: String) : FooClient {
  
 private val client = HttpClient(CIO)

 override fun call(name: String): String = runBlocking {
  try {
   client.get("$url/foo") {
    parameter("name", name)
   }
  } catch (e: Exception) {
   "Foo api error: ${e.message}"
  }
 }
}

AppUseCase

Now we have to implement AppUseCase, which will use a FooClient to call the Foo API and then a BarClient to call the Bar API.

As it is not WireMock related because we can test first the implementation just using MockK JUnit5 extension we can skip the details and you can review the source code of AppUseCaseShould and AppUseCase.

App

App implementation

Let me introduce first the App implementation, as I will present later two different types of WireMock tests:

class App(
 private val name: String,
 private val fooApiUrl: String,
 private val barApiUrl: String
) {

 fun execute() = AppUseCase().execute(
  name,
  FooKtorClient(fooApiUrl),
  BarKtorClient(barApiUrl)
 )
}

App test with @WireMockTest

Since in this example Foo API and Bar API do not have conflicting endpoints, we can use one @WireMockTest to mock both APIs:

@WireMockTest
class AppShouldWithOneWireMockTest {

 private val name = "Ada"

 @Test
 fun `call foo and bar`(wm: WireMockRuntimeInfo) {
  stubFor(
   get(urlPathEqualTo("/foo"))
    .withQueryParam("name", equalTo(name))
    .willReturn(ok().withBody("Hello $name I am Foo!"))
  )
  stubFor(
   get(urlPathMatching("/bar/$name"))
    .willReturn(ok().withBody("Hello $name I am Bar!"))
  )

  val app = App(name, wm.httpBaseUrl, wm.httpBaseUrl)

  assertThat(app.execute()).isEqualTo(
   """
    Hi! I am $name
    I called Foo and its response is Hello $name I am Foo!
    I called Bar and its response is Hello $name I am Bar!
    Bye!
   """.trimIndent()
  )
 }
}

App test with WireMockExtension

But imagine a real scenario where Foo API and Bar API do have conflicting endpoints, or you just want to mock them separatedly for any reason. In this case you can register two WireMockExtensions instead of using @WireMockTest:

@TestInstance(PER_CLASS)
class AppShouldWithTwoWireMockExtensions {

 private val name = "Leo"

 @RegisterExtension
 val wireMockFoo: WireMockExtension = newInstance().build()

 @RegisterExtension
 val wireMockBar: WireMockExtension = newInstance().build()

 @Test
 fun `call foo and bar`() {
  wireMockFoo.stubFor(
   get(WireMock.urlPathEqualTo("/foo"))
    .withQueryParam("name", equalTo(name))
    .willReturn(ok().withBody("Hello $name I am Foo!"))
  )
  wireMockBar.stubFor(
   get(WireMock.urlPathMatching("/bar/$name"))
    .willReturn(ok().withBody("Hello $name I am Bar!"))
  )

  val app = App(name, wireMockFoo.baseUrl(), wireMockBar.baseUrl())

  assertThat(app.execute()).isEqualTo(
   """
    Hi! I am $name
    I called Foo and its response is Hello $name I am Foo!
    I called Bar and its response is Hello $name I am Bar!
    Bye!
   """.trimIndent()
  )
 }
}

App test with WireMock Docker

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
class AppShouldWithWireMockDocker {

 private val name = "Ivy"

 private val fooServiceName = "foo-api"
 private val fooServicePort = 8080
 private val barServiceName = "bar-api"
 private val barServicePort = 8080

 @Container
 val container = DockerComposeContainer<Nothing>(File("docker-compose.yml"))
  .apply {
   withLocalCompose(true)
   withExposedService(fooServiceName, fooServicePort, Wait.forListeningPort())
   withExposedService(barServiceName, barServicePort, Wait.forListeningPort())
  }

 @Test
 fun `call foo and bar`() {
  val fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
  val fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
  val barApiHost = container.getServiceHost(barServiceName, barServicePort)
  val barApiPort = container.getServicePort(barServiceName, barServicePort)

  val fooApiUrl = "http://${fooApiHost}:${fooApiPort}"
  val barApiUrl = "http://${barApiHost}:${barApiPort}"

  val app = App(name, fooApiUrl, barApiUrl)

  assertThat(app.execute()).isEqualTo(
   """
    Hi! I am $name
    I called Foo and its response is Hello $name I am Foo!
    I called Bar and its response is Hello $name I am Bar!
    Bye!
   """.trimIndent()
  )
 }
}

With this testing approach we cannot configure our stubs programmatically like we did in testing with @WireMockTest or testing with WireMockExtension. Instead, we have to configure them as json files under mappings directory and we have to use mechanisms such as response templating or stateful behaviour.

App run with WireMock Docker

WireMock with Docker has a cool advantage, we can use the same docker-compose used by the test to start the application and run/debug it locally:

WireMockDockerRun

In this case we only need to use fixed ports, configuring them in docker-compose.override.yml. This override does not affect @Testcontainers.

That was a good one! Happy coding! 💙

Test this demo

./gradlew test

Run this demo

docker compose up -d
./gradlew run
docker compose down
You might also like...
A Kotlin binding to webview, a tiny cross-platform webview library, supports Java and Native.
A Kotlin binding to webview, a tiny cross-platform webview library, supports Java and Native.

webviewko provides a Kotlin/JVM and a Kotlin/Native(experimental) binding to webview, a tiny cross-platform webview library to build modern cross-platform GUIs using WebView2, WebKit and WebKitGTK.

 App which show comic and characters using marvel apis
App which show comic and characters using marvel apis

Marvel App App which show comic and characters using marvel apis ScreenShot Tech Room database MVVM Architecture Navigation Component How to run proje

A news app made using android studio in Java with features like favourite news, Location detector for local news, and especially made with HUAWEI APIs

HuaweiGlobalNewsApp A news app made using android studio in Java with features like favourite news, Location detector for local news, and especially m

Android application that implements location and network related Android APIs

Location and network data collection Location and network data collection with Android's telephonyManager class. Introduction Technologies Android's A

NativeScript empowers you to access native platform APIs from JavaScript directly. Angular, Capacitor, Ionic, React, Svelte, Vue and you name it compatible.
NativeScript empowers you to access native platform APIs from JavaScript directly. Angular, Capacitor, Ionic, React, Svelte, Vue and you name it compatible.

NativeScript empowers you to access native APIs from JavaScript directly. The framework currently provides iOS and Android runtimes for rich mobile de

GitHub application fetches events, repositories and profile using GitHub APIs
GitHub application fetches events, repositories and profile using GitHub APIs

GitHub application using GitHub REST API Dagger MVVM architecture Mockk Jetpack Compose Kotlin Coroutines Application pages Attention If you want to u

Example of a multimodule project following SOLID principles and MVVM, Hilt, Room, coroutines and testing.
Example of a multimodule project following SOLID principles and MVVM, Hilt, Room, coroutines and testing.

MarvelCharacters David Ferrándiz Features Retrieve Marvel’s characters and show them in a grid. See more information about the character in a new scre

sample codebase for E2E testing with Jest, Appium and WebDriverIO for Android and iOS

E2E testing for React Native with Jest, Appium and WebDriverIO (iOS and Android) In this repo you will find a sample project to showcase how to do E2E

Android samples for Google Workspace APIs

Google Workspace Android Samples A collection of samples that demonstrate how to call Google Workspace APIs from Android. Products Drive Deprecation A

Owner
Roger Viñas
Roger Viñas
MVVM + Kotlin + Jetpack Compose +Navigation Compose + Hilt + Retrofit + Unit Testing + Compose Testing + Coroutines + Kotlin Flow + Io mockK

MvvmKotlinJetpackCompose Why do we need an architecture even when you can make an app without it? let's say you created a project without any architec

Sayyed Rizwan 46 Nov 29, 2022
Registration validation testing, Room database testing using JUnit4

Notes app Registration details validation testing, Room database testing using JUnit4 ✨ Screenshots Authors Tridev Deka - LinkedIn - Tridev Deka MIT L

Tridev Deka 0 Mar 30, 2022
A curated list of great Android lint custom rules and resources

Awesome Android Lint A curated list of great Android lint custom rules and resources Contents Rules Articles Other Contribute Rules Google Sample Cust

Harold Martin 37 Dec 9, 2022
The App Loads list of popular movies from a mock API and shows in a recyclerView

popular-movies-app About The App Loads list of popular movies from a mock API and shows in a recyclerView. Any item can be clicked to open the Movie D

Mayank Agarwal 0 Oct 30, 2021
Xposed module which will set location where you want without effect mock location.

GPS Setter Support/Discussion: XDA thread As most of GPS spoof app not working anymore coz some are old and some are not proper implement with current

Android1500 73 Dec 28, 2022
The easy way to use biometric authentication in your Flutter app. Supports Fingerprint, FaceID and Iris.

BiometricX The easy way to use biometric authentication in your Flutter app. Supports Fingerprint, FaceID and Iris. Demo APK. Starting $ flutter pub a

Salman S 13 Dec 15, 2022
Android playground project with modularization by feature (android libraries), unit tests, MVVM & MVI.

Movies Movies is a simple project to study and play with some android components, architecture and tools for Android development. Tech Stack This proj

Christopher Elias 333 Dec 30, 2022
A simple word quiz app to prepare for English proficiency tests

Vocab-Buddy Vocab Buddy is a simple, free, and ad-free word quiz app that is designed to help users exercise/improve their vocabulary for globally rec

Dishant Behera 8 Jun 11, 2022
A demo project showcasing different exercises + details with prepopulated data from room. Includes tests

README Pre-requisites I used Android Studio Bumblebee (2021.1.1). In case of any issues, do let me know because I understand that issues might vary fo

Carol 24 Nov 17, 2022