{"id":15403621,"url":"https://github.com/rogervinas/wiremock-testing","last_synced_at":"2025-04-16T03:42:32.675Z","repository":{"id":39917562,"uuid":"451817623","full_name":"rogervinas/wiremock-testing","owner":"rogervinas","description":"🤹 WireMock Testing","archived":false,"fork":false,"pushed_at":"2025-02-19T11:37:59.000Z","size":889,"stargazers_count":4,"open_issues_count":0,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-19T12:31:52.968Z","etag":null,"topics":["kotlin","testcontainers","testing","wiremock"],"latest_commit_sha":null,"homepage":"https://dev.to/rogervinas/testing-with-wiremocktest-3da0","language":"Kotlin","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rogervinas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-25T09:41:12.000Z","updated_at":"2025-02-19T11:38:01.000Z","dependencies_parsed_at":"2023-12-11T18:24:56.735Z","dependency_job_id":"a31f7eda-4b0e-45b8-a79a-21e16a672c94","html_url":"https://github.com/rogervinas/wiremock-testing","commit_stats":{"total_commits":98,"total_committers":3,"mean_commits":"32.666666666666664","dds":0.3571428571428571,"last_synced_commit":"f9b81bac4ab141c72fd879bc0f06476058af900e"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogervinas%2Fwiremock-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogervinas%2Fwiremock-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogervinas%2Fwiremock-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rogervinas%2Fwiremock-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rogervinas","download_url":"https://codeload.github.com/rogervinas/wiremock-testing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241341388,"owners_count":19947099,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","testcontainers","testing","wiremock"],"created_at":"2024-10-01T16:09:29.055Z","updated_at":"2025-04-16T03:42:32.649Z","avatar_url":"https://github.com/rogervinas.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/rogervinas/wiremock-testing/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/rogervinas/wiremock-testing/actions/workflows/ci.yml)\n![Java](https://img.shields.io/badge/Java-21-blue?labelColor=black)\n![Kotlin](https://img.shields.io/badge/Kotlin-2.x-blue?labelColor=black)\n![WireMock](https://img.shields.io/badge/WireMock-3.12.1-blue?labelColor=black)\n\n# WireMock Testing\n\n[WireMock](https://wiremock.org/) is a great library to mock APIs in your tests and supports [Junit5](https://wiremock.org/docs/junit-jupiter/) with two modes:\n- Declarative with **@WireMockTest**\n- Programmatic with **WireMockExtension**\n\nAnd **WireMock** also has:\n- A [Docker image](https://hub.docker.com/r/wiremock/wiremock)!\n- A [Testcontainers module](https://wiremock.org/docs/solutions/testcontainers/)!\n\n(you can also check [other supported technologies](https://wiremock.org/docs/#:~:text=By%20technology))\n\nBut \"talk is cheap, show me the code [...](https://www.goodreads.com/quotes/437173-talk-is-cheap-show-me-the-code#:~:text=Quote%20by%20Linus%20Torvalds%3A%20%E2%80%9CTalk,Show%20me%20the%20code.%E2%80%9D)\" 😮\n\nOk so let's implement first the scenario with **@WireMockTest**:\n\n![WireMockTest](doc/WireMockTest.png)\n\nAnd later the one with [@Testcontainers](https://testcontainers.com/) and these two alternatives:\n1. Generic [Compose Testcontainers module](https://java.testcontainers.org/modules/docker_compose/#compose-v2) using [official WireMock's docker image](https://hub.docker.com/r/wiremock/wiremock)\n2. [WireMock's Testcontainers module](https://wiremock.org/docs/solutions/testcontainers/)\n\n![WireMockDockerTest](doc/WireMockDockerTest.png)\n\n* [BarClient](#barclient)\n  * [BarClient interface](#barclient-interface)\n  * [BarKtorClient test](#barktorclient-test)\n  * [BarKtorClient implementation](#barktorclient-implementation)\n* [FooClient](#fooclient)\n  * [FooClient interface](#fooclient-interface)\n  * [FooKtorClient test](#fooktorclient-test)\n  * [FooKtorClient implementation](#fooktorclient-implementation)\n* [AppUseCase](#appusecase)\n* [App](#app)\n  * [App implementation](#app-implementation)\n  * [App test with @WireMockTest](#app-test-with-wiremocktest)\n  * [App test with WireMockExtension](#app-test-with-wiremockextension)\n  * [App test with Compose Testcontainers module](#app-test-with-compose-testcontainers-module)\n    * [Static stubs](#static-stubs)\n    * [Dynamic stubs](#dynamic-stubs)\n  * [App test with WireMock Testcontainers module](#app-test-with-wiremock-testcontainers-module)\n  * [App run with WireMock container and Docker Compose](#app-run-with-wiremock-container-and-docker-compose)\n* [Test this demo](#test-this-demo)\n* [Run this demo](#run-this-demo)\n\n## BarClient\n\n### BarClient interface\n\n```kotlin\ninterface BarClient {\n  fun call(name: String): String\n}\n```\n\n### BarKtorClient test\n\nI will use a [Ktor client](https://ktor.io/docs/client.html) for no other reason that I need an Http client and this seems interesting, as we are using **Kotlin**.\n\nSo a simple **@WireMockTest** for the **BarKtorClient** looks like:\n\n```kotlin\n@WireMockTest\nclass BarKtorClientShould {\n\n private val name = \"Sue\"\n \n @Test\n fun `call bar api`(wm: WireMockRuntimeInfo) {\n  stubFor(\n   get(urlPathMatching(\"/bar/$name\"))\n   .willReturn(ok().withBody(\"Hello $name I am Bar!\"))\n  )\n\n  assertThat(\n    BarKtorClient(wm.httpBaseUrl).call(name)\n  ).isEqualTo(\"Hello $name I am Bar!\")\n }\n\n @Test\n fun `handle bar api server error`(wm: WireMockRuntimeInfo) {\n  stubFor(\n   get(urlPathMatching(\"/bar/.+\"))\n   .willReturn(serverError())\n  )\n\n  assertThat(BarKtorClient(wm.httpBaseUrl).call(name))\n   .startsWith(\"Bar api error: Server error\")\n }\n}\n```\n\n### BarKtorClient implementation\n\nIn order to make the test pass 🟩 we can implement the **BarKtorClient** this way:\n\n```kotlin\nclass BarKtorClient(private val url: String) : BarClient {\n\n private val client = HttpClient(CIO) {\n   expectSuccess = true\n }\n\n override fun call(name: String): String = runBlocking {\n  try {\n   client.get(\"$url/bar/$name\").body\u003cString\u003e()\n  } catch (e: Exception) {\n   \"Bar api error: ${e.message}\"\n  }\n }\n}\n```\n\n## FooClient\n\n### FooClient interface\n\n```kotlin\ninterface FooClient {\n  fun call(name: String): String\n}\n```\n\n### FooKtorClient test\n\nFor this test I want to use [WireMock's response templating](https://wiremock.org/docs/response-templating/) feature, so I will register a **WireMockExtension** instead of using **@WireMockTest**:\n\n```kotlin\n@TestInstance(PER_CLASS)\nclass FooKtorClientShould {\n  \n private val name = \"Joe\"\n\n @RegisterExtension\n val wm: WireMockExtension = WireMockExtension.newInstance()\n  .options(options().globalTemplating(true))\n  .configureStaticDsl(true)\n  .build()\n\n @Test\n fun `call foo api`() {\n  stubFor(\n   get(urlPathEqualTo(\"/foo\"))\n   .withQueryParam(\"name\", matching(\".+\"))\n   .willReturn(ok().withBody(\"Hello {{request.query.name}} I am Foo!\"))\n  )\n\n  assertThat(FooKtorClient(wm.baseUrl()).call(name))\n   .isEqualTo(\"Hello $name I am Foo!\")\n }\n\n @Test\n fun `handle foo api server error`() {\n  stubFor(\n   get(urlPathEqualTo(\"/foo\"))\n   .willReturn(WireMock.serverError())\n  )\n\n  assertThat(FooKtorClient(wm.baseUrl()).call(name))\n   .startsWith(\"Foo api error: Server error\")\n }\n}\n```\n\nNote that:\n* Instead of having a fixed response, with [WireMock's response templating](https://wiremock.org/docs/response-templating/) we can insert in the response values from the request. In this case the query parameter `name`.\n* `@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](https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle)).\n* `configureStaticDsl(true)` makes it possible to use the static DSL, that is using `stubFor(...)` staticly instead of `wm.stubFor(...)`.\n\n### FooKtorClient implementation\n\nSame as before in order to make the test pass 🟩 we can implement the **FooKtorClient** this way:\n\n```kotlin\nclass FooKtorClient(private val url: String) : FooClient {\n  \n private val client = HttpClient(CIO) {\n   expectSuccess = true\n }\n\n override fun call(name: String): String = runBlocking {\n  try {\n   client.get(\"$url/foo\") {\n    parameter(\"name\", name)\n   }.body\u003cString\u003e()\n  } catch (e: Exception) {\n   \"Foo api error: ${e.message}\"\n  }\n }\n}\n```\n\n## AppUseCase\n\nNow we have to implement **AppUseCase**, which will use a **FooClient** to call the **Foo API** and then a **BarClient** to call the **Bar API**. \n\nAs it is not **WireMock** related because we can test first the implementation just using [MockK JUnit5 extension](https://mockk.io/#junit5) we can skip the details and you can review the source code of [AppUseCaseShould](src/test/kotlin/com/rogervinas/wiremock/AppUseCaseShould.kt) and [AppUseCase](src/main/kotlin/com/rogervinas/wiremock/AppUseCase.kt).\n\n## App\n\n### App implementation\n\nLet me introduce first the **App** implementation, as I will present later two different types of **WireMock** tests:\n\n```kotlin\nclass App(\n private val name: String,\n private val fooApiUrl: String,\n private val barApiUrl: String\n) {\n\n fun execute() = AppUseCase().execute(\n  name,\n  FooKtorClient(fooApiUrl),\n  BarKtorClient(barApiUrl)\n )\n}\n```\n\n### App test with @WireMockTest\n\nSince in this example **Foo API** and **Bar API** \u003cu\u003edo not have conflicting endpoints\u003c/u\u003e, we can use one **@WireMockTest** to mock both APIs:\n\n```kotlin\n@WireMockTest\nclass AppShouldWithOneWireMockTest {\n\n private val name = \"Ada\"\n\n @Test\n fun `call foo and bar`(wm: WireMockRuntimeInfo) {\n  stubFor(\n   get(urlPathEqualTo(\"/foo\"))\n    .withQueryParam(\"name\", equalTo(name))\n    .willReturn(ok().withBody(\"Hello $name I am Foo!\"))\n  )\n  stubFor(\n   get(urlPathMatching(\"/bar/$name\"))\n    .willReturn(ok().withBody(\"Hello $name I am Bar!\"))\n  )\n\n  val app = App(name, wm.httpBaseUrl, wm.httpBaseUrl)\n\n  assertThat(app.execute()).isEqualTo(\n   \"\"\"\n    Hi! I am $name\n    I called Foo and its response is Hello $name I am Foo!\n    I called Bar and its response is Hello $name I am Bar!\n    Bye!\n   \"\"\".trimIndent()\n  )\n }\n}\n```\n\n### App test with WireMockExtension\n\nBut imagine a real scenario where **Foo API** and **Bar API** \u003cu\u003edo have conflicting endpoints\u003c/u\u003e, or you just want to \u003cu\u003emock them separatedly for any reason\u003c/u\u003e. In this case you can register two **WireMockExtensions** instead of using **@WireMockTest**:\n\n```kotlin\n@TestInstance(PER_CLASS)\nclass AppShouldWithTwoWireMockExtensions {\n\n private val name = \"Leo\"\n\n @RegisterExtension\n val wireMockFoo: WireMockExtension = newInstance().build()\n\n @RegisterExtension\n val wireMockBar: WireMockExtension = newInstance().build()\n\n @Test\n fun `call foo and bar`() {\n  wireMockFoo.stubFor(\n   get(WireMock.urlPathEqualTo(\"/foo\"))\n    .withQueryParam(\"name\", equalTo(name))\n    .willReturn(ok().withBody(\"Hello $name I am Foo!\"))\n  )\n  wireMockBar.stubFor(\n   get(WireMock.urlPathMatching(\"/bar/$name\"))\n    .willReturn(ok().withBody(\"Hello $name I am Bar!\"))\n  )\n\n  val app = App(name, wireMockFoo.baseUrl(), wireMockBar.baseUrl())\n\n  assertThat(app.execute()).isEqualTo(\n   \"\"\"\n    Hi! I am $name\n    I called Foo and its response is Hello $name I am Foo!\n    I called Bar and its response is Hello $name I am Bar!\n    Bye!\n   \"\"\".trimIndent()\n  )\n }\n}\n```\n\n### App test with Compose Testcontainers module\n\n#### Static stubs\n\nFirst we will use static stubs configured as json files:\n\nIn our [docker-compose.yml](docker-compose.yml):\n* We configure two **WireMock** containers, one for **Foo API** and one for **Bar API**.\n* We use dynamic ports for each container.\n* We enable [response templating](https://wiremock.org/docs/response-templating/) adding the parameter `--global-response-templating` (see [command line options](https://wiremock.org/docs/running-standalone/)).\n* We mount as volumes the directories containing the **WireMock** mappings: [foo-api/mappings](wiremock/foo-api/mappings) and [bar-api/mappings](wiremock/bar-api/mappings).\n\nFinally we test the **App** using [Testcontainers JUnit5 extension](https://www.testcontainers.org/test_framework_integration/junit_5/):\n\n```kotlin\n@Testcontainers\n@TestInstance(PER_CLASS)\nclass AppShouldWithComposeTestcontainers {\n\n companion object {\n  private const val NAME = \"Ivy\" \n  private const val FOO_SERVICE_NAME = \"foo-api\"\n  private const val FOO_SERVICE_PORT = 8080\n  private const val BAR_SERVICE_NAME = \"bar-api\"\n  private const val BAR_SERVICE_PORT = 8080\n  private lateinit var fooApiHost: String\n  private var fooApiPort: Int = 0\n  private lateinit var barApiHost: String\n  private var barApiPort: Int = 0\n   \n  @Container\n  @JvmStatic\n  val container = ComposeContainer(File(\"docker-compose.yml\"))\n    .withLocalCompose(true)\n    .withExposedService(FOO_SERVICE_NAME, FOO_SERVICE_PORT, forListeningPort())\n    .withExposedService(BAR_SERVICE_NAME, BAR_SERVICE_PORT, forListeningPort())\n   \n  @BeforeAll\n  @JvmStatic\n  fun beforeAll() {\n    fooApiHost = container.getServiceHost(FOO_SERVICE_NAME, FOO_SERVICE_PORT)\n    fooApiPort = container.getServicePort(FOO_SERVICE_NAME, FOO_SERVICE_PORT)\n    barApiHost = container.getServiceHost(BAR_SERVICE_NAME, BAR_SERVICE_PORT)\n    barApiPort = container.getServicePort(BAR_SERVICE_NAME, BAR_SERVICE_PORT)\n  }\n }\n\n @Test\n fun `call foo and bar`() {\n  val fooApiUrl = \"http://${fooApiHost}:${fooApiPort}\"\n  val barApiUrl = \"http://${barApiHost}:${barApiPort}\"\n\n  val app = App(name, fooApiUrl, barApiUrl)\n\n  assertThat(app.execute()).isEqualTo(\n   \"\"\"\n    Hi! I am $name\n    I called Foo and its response is Hello $name I am Foo!\n    I called Bar and its response is Hello $name I am Bar!\n    Bye!\n   \"\"\".trimIndent()\n  )\n }\n}\n```\n\n#### Dynamic stubs\n\nWe can also configure our stubs programmatically like we did in [testing with @WireMockTest](#app-test-with-wiremocktest) or [testing with WireMockExtension](#app-test-with-wiremockextension).\n\nTo do so we have to use the [WireMock client](https://wiremock.org/docs/java-usage) and connect it to the [WireMock Admin API](https://wiremock.org/docs/api/) of the two **WireMock** containers:\n\n```kotlin\n@Test\nfun `call foo an bar with dynamic stubs`() {\n val fooApiUrl = \"http://${fooApiHost}:${fooApiPort}/dynamic\"\n val barApiUrl = \"http://${barApiHost}:${barApiPort}/dynamic\"\n\n  WireMock(fooApiHost, fooApiPort)\n    .register(\n      get(urlPathEqualTo(\"/dynamic/foo\"))\n        .withQueryParam(\"name\", WireMock.equalTo(name))\n        .willReturn(ok().withBody(\"Hi $name I am Foo, how are you?\"))\n    )\n  WireMock(barApiHost, barApiPort)\n    .register(\n      get(urlPathMatching(\"/dynamic/bar/$name\"))\n        .willReturn(ok().withBody(\"Hi $name I am Bar, nice to meet you!\"))\n    )\n \n val app = App(name, fooApiUrl, barApiUrl)\n assertThat(app.execute()).isEqualTo(\n   \"\"\"\n     Hi! I am $name\n     I called Foo and its response is Hi $name I am Foo, how are you?\n     I called Bar and its response is Hi $name I am Bar, nice to meet you!\n     Bye!\n   \"\"\".trimIndent()\n )\n}\n```\n\n### App test with WireMock Testcontainers module\n\nInstead of the generic **ComposeContainer** we can use the specific **WireMockContainer** this way:\n\n```kotlin\n@Testcontainers\n@TestInstance(PER_CLASS)\nclass AppShouldWithWireMockTestcontainers {\n\n  companion object {\n    @Container\n    @JvmStatic\n    val containerFoo = WireMockContainer(\"wiremock/wiremock:3.2.0\")\n      .withMappingFromJSON(File(\"wiremock/foo-api/mappings/foo-get.json\").readText())\n      .withCliArg(\"--global-response-templating\")\n\n    @Container\n    @JvmStatic\n    val containerBar = WireMockContainer(\"wiremock/wiremock:3.2.0\")\n      .withMappingFromJSON(File(\"wiremock/bar-api/mappings/bar-get.json\").readText())\n      .withCliArg(\"--global-response-templating\")\n  }\n\n  @Test\n  fun `call foo and bar`() {\n    val fooApiUrl = \"http://${containerFoo.host}:${containerFoo.port}\"\n    val barApiUrl = \"http://${containerBar.host}:${containerBar.port}\"\n    // ...\n  }\n\n  @Test\n  fun `call foo an bar with dynamic stubs`() {\n    // ...\n  }\n}\n```\n\nTests are the same as the ones in [App test with Compose Testcontainers module](#app-test-with-compose-testcontainers-module), just with two minor differences:\n- The way we get `host` and `port` for each container\n- The way we specify `--global-response-templating` parameter to enable [response templating](https://wiremock.org/docs/response-templating/) \n\n### App run with WireMock container and Docker Compose\n\nWe can use the same **docker-compose** used by the test to start the application and run/debug it locally:\n\n![WireMockDockerRun](doc/WireMockDockerRun.png)\n\nIn this case we only need to use fixed ports, configuring them in [docker-compose.override.yml](docker-compose.override.yml). This override does not affect **@Testcontainers**.\n\nThat was a good one! Happy coding! 💙\n\n## Test this demo\n\n```shell\n./gradlew test\n```\n\n## Run this demo\n\n```\ndocker compose up -d\n./gradlew run\ndocker compose down\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frogervinas%2Fwiremock-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frogervinas%2Fwiremock-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frogervinas%2Fwiremock-testing/lists"}