Skip to content

Commit

Permalink
Use WireMock Testcontainers (#24)
Browse files Browse the repository at this point in the history
* Update documentation

* Fix text

* Use numbered list

* Fix image

* Fix text

* Fix link

* Fix link

* Fix code

* Fix index

* Fix links

* Rename test

* Use WireMockContainer

* Update README

* Fix headings
  • Loading branch information
rogervinas authored Nov 7, 2023
1 parent e1473f7 commit c8a12cd
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 22 deletions.
80 changes: 62 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@
# WireMock Testing

[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:

- Declarative with **@WireMockTest**
- Programmatic with **WireMockExtension**

And **WireMock** also has an [official Docker image](https://hub.docker.com/r/wiremock/wiremock)!
And **WireMock** also has:
- A [Docker image](https://hub.docker.com/r/wiremock/wiremock)!
- A [Testcontainers module](https://wiremock.org/docs/solutions/testcontainers/)!

(you can also check [other supported technologies](https://wiremock.org/docs/#:~:text=By%20technology))

But "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)" 😮

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

![WireMockTest](doc/WireMockTest.png)

And later the one with [WireMock's official Docker image](https://hub.docker.com/r/wiremock/wiremock):
And later the one with [@Testcontainers](https://testcontainers.com/) and these two alternatives:
1. 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)
2. [WireMock's Testcontainers module](https://wiremock.org/docs/solutions/testcontainers/)

![WireMockDockerTest](doc/WireMockDockerTest.png)

Expand All @@ -35,10 +40,11 @@ And later the one with [WireMock's official Docker image](https://hub.docker.com
* [App implementation](#app-implementation)
* [App test with @WireMockTest](#app-test-with-wiremocktest)
* [App test with WireMockExtension](#app-test-with-wiremockextension)
* [App test with WireMock Docker](#app-test-with-wiremock-docker)
* [App test with Compose Testcontainers module](#app-test-with-compose-testcontainers-module)
* [Static stubs](#static-stubs)
* [Dynamic stubs](#dynamic-stubs)
* [App run with WireMock Docker](#app-run-with-wiremock-docker)
* [App test with WireMock Testcontainers module](#app-test-with-wiremock-testcontainers-module)
* [App run with WireMock container and Docker Compose](#app-run-with-wiremock-container-and-docker-compose)
* [Test this demo](#test-this-demo)
* [Run this demo](#run-this-demo)

Expand Down Expand Up @@ -294,9 +300,9 @@ class AppShouldWithTwoWireMockExtensions {
}
```

## App test with WireMock Docker
### App test with Compose Testcontainers module

### Static stubs
#### Static stubs

First we will use static stubs configured as json files:

Expand All @@ -311,7 +317,7 @@ Finally we test the **App** using [Testcontainers JUnit5 extension](https://www.
```kotlin
@Testcontainers
@TestInstance(PER_CLASS)
class AppShouldWithWireMockDocker {
class AppShouldWithComposeTestcontainers {

companion object {
private const val name = "Ivy"
Expand All @@ -326,13 +332,10 @@ class AppShouldWithWireMockDocker {

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

@BeforeAll
@JvmStatic
Expand Down Expand Up @@ -363,7 +366,7 @@ class AppShouldWithWireMockDocker {
}
```

### Dynamic stubs
#### Dynamic stubs

We 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).

Expand Down Expand Up @@ -397,9 +400,50 @@ fun `call foo an bar with dynamic stubs`() {
}
```

## App run with WireMock Docker
### App test with WireMock Testcontainers module

Instead of the generic **ComposeContainer** we can use the specific **WireMockContainer** this way:

```kotlin
@Testcontainers
@TestInstance(PER_CLASS)
class AppShouldWithWireMockTestcontainers {

companion object {
@Container
@JvmStatic
val containerFoo = WireMockContainer("wiremock/wiremock:3.2.0")
.withMappingFromJSON(File("wiremock/foo-api/mappings/foo-get.json").readText())
.withCliArg("--global-response-templating")

@Container
@JvmStatic
val containerBar = WireMockContainer("wiremock/wiremock:3.2.0")
.withMappingFromJSON(File("wiremock/bar-api/mappings/bar-get.json").readText())
.withCliArg("--global-response-templating")
}

@Test
fun `call foo and bar`() {
val fooApiUrl = "http://${containerFoo.host}:${containerFoo.port}"
val barApiUrl = "http://${containerBar.host}:${containerBar.port}"
// ...
}

@Test
fun `call foo an bar with dynamic stubs`() {
// ...
}
}
```

Tests are the same as the ones in [App test with Compose Testcontainers module](#app-test-with-compose-testcontainers-module), just with two minor differences:
- The way we get `host` and `port` for each container
- The way we specify `--global-response-templating` parameter to enable [response templating](https://wiremock.org/docs/response-templating/)

### App run with WireMock container and Docker Compose

**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:
We can use the same **docker-compose** used by the test to start the application and run/debug it locally:

![WireMockDockerRun](doc/WireMockDockerRun.png)

Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
testImplementation("io.mockk:mockk:1.12.2")
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("org.wiremock:wiremock:3.3.1")
testImplementation("org.wiremock.integrations.testcontainers:wiremock-testcontainers-module:1.0-alpha-13")
testImplementation("org.testcontainers:testcontainers:1.19.1")
testImplementation("org.testcontainers:junit-jupiter:1.19.1")
}
Expand Down
Binary file modified doc/WireMockDockerTest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
services:

foo-api:
image: wiremock/wiremock:2.32.0
image: wiremock/wiremock:3.2.0
ports:
- "8080"
command:
Expand All @@ -10,7 +10,7 @@ services:
- ./wiremock/foo-api:/home/wiremock

bar-api:
image: wiremock/wiremock:2.32.0
image: wiremock/wiremock:3.2.0
ports:
- "8080"
command:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.rogervinas.wiremock

import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.client.WireMock.get
import com.github.tomakehurst.wiremock.client.WireMock.ok
import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo
import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
Expand All @@ -15,7 +18,7 @@ import java.io.File

@Testcontainers
@TestInstance(PER_CLASS)
class AppShouldWithWireMockDocker {
class AppShouldWithComposeTestcontainers {

companion object {
private const val name = "Ivy"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.rogervinas.wiremock

import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.get
import com.github.tomakehurst.wiremock.client.WireMock.ok
import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo
import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.wiremock.integrations.testcontainers.WireMockContainer
import java.io.File

@Testcontainers
@TestInstance(PER_CLASS)
class AppShouldWithWireMockTestcontainers {

companion object {
private const val name = "Ivy"

@Container
@JvmStatic
val containerFoo = WireMockContainer("wiremock/wiremock:3.2.0")
.withMappingFromJSON(File("wiremock/foo-api/mappings/foo-get.json").readText())
.withCliArg("--global-response-templating")

@Container
@JvmStatic
val containerBar = WireMockContainer("wiremock/wiremock:3.2.0")
.withMappingFromJSON(File("wiremock/bar-api/mappings/bar-get.json").readText())
.withCliArg("--global-response-templating")
}

@Test
fun `call foo and bar`() {
val fooApiUrl = "http://${containerFoo.host}:${containerFoo.port}"
val barApiUrl = "http://${containerBar.host}:${containerBar.port}"

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()
)
}

@Test
fun `call foo an bar with dynamic stubs`() {
val fooApiUrl = "http://${containerFoo.host}:${containerFoo.port}/dynamic"
val barApiUrl = "http://${containerBar.host}:${containerBar.port}/dynamic"

WireMock(containerFoo.host, containerFoo.port)
.register(get(urlPathEqualTo("/dynamic/foo"))
.withQueryParam("name", WireMock.equalTo(name))
.willReturn(ok().withBody("Hi $name I am Foo, how are you?"))
)
WireMock(containerBar.host, containerBar.port)
.register(get(urlPathMatching("/dynamic/bar/$name"))
.willReturn(ok().withBody("Hi $name I am Bar, nice to meet you!"))
)

val app = App(name, fooApiUrl, barApiUrl)

assertThat(app.execute()).isEqualTo(
"""
Hi! I am $name
I called Foo and its response is Hi $name I am Foo, how are you?
I called Bar and its response is Hi $name I am Bar, nice to meet you!
Bye!
""".trimIndent()
)
}
}

0 comments on commit c8a12cd

Please sign in to comment.