Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

CIJavaKotlinMockServer

MockServer Testing

I've just found out MockServer and it looks awesome 🀩 so I wanted to check it out repeating the steps of my previous demo WireMock Testing which (as you can expect) uses WireMock, another fantastic tool to mock APIs.

So in this demo we will use first MockServer JUnit5 extension:

MockServerTest

And then we will use MockServer Docker image and Testcontainers:

MockServerDockerTest

BarClient

BarClient interface

interfaceBarClient {
funcall(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 test with MockServerExtension for the BarKtorClient looks like:

@ExtendWith(MockServerExtension::class)
@TestInstance(PER_CLASS)
classBarKtorClientShould {
privateval name ="Sue"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll(mockServerClient:MockServerClient) {
this.mockServerClient = mockServerClient
this.mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call bar api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
assertThat(BarKtorClient(mockServerUrl).call(name))
.isEqualTo("Hello $name I am Bar!")
}
@Test
fun`handle bar api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/.+"))
.respond(response().withStatusCode(503))
assertThat(BarKtorClient(mockServerUrl).call(name))
.startsWith("Bar api error: Server error")
}
}

Note that we can inject MockServerClient as a parameter in the test methods too.

BarKtorClient implementation

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

classBarKtorClient(privatevalurl:String) : BarClient {
privateval client =HttpClient(CIO)
overridefuncall(name:String): String= runBlocking {
try {
client.get("$url/bar/$name")
} catch (e:Exception) {
"Bar api error: ${e.message}"
}
}
}

FooClient

FooClient interface

interfaceFooClient {
funcall(name:String): String
}

FooKtorClient test

For this test we will use MockServer's Mustache templates:

@TestInstance(PER_CLASS)
classFooKtorClientShould {
privateval name ="Joe"privatelateinitvar mockServerClient:MockServerClientprivatelateinitvar mockServerUrl:String
@BeforeAll
funbeforeAll() {
mockServerClient =ClientAndServer()
mockServerUrl ="http://localhost:${mockServerClient.port}"
}
@BeforeEach
funbeforeEach() {
mockServerClient.reset()
}
@Test
fun`call foo api`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(template(
MUSTACHE,
""" { statusCode: 200, body: 'Hello {{ request.queryStringParameters.name.0 }} I am Foo!' }""".trimIndent()
))
assertThat(FooKtorClient(mockServerUrl).call(name)).isEqualTo("Hello $name I am Foo!")
}
@Test
fun`handle foo api server error`() {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", ".+"))
.respond(response().withStatusCode(503))
assertThat(FooKtorClient(mockServerUrl).call(name)).startsWith("Foo api error: Server error")
}
}

Note that:

  • As in the previous test we can inject MockServerClient as a parameter in the test methods too.
  • MockServer provides also Velocity and Javascript templates that support more complex logic.

FooKtorClient implementation

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

classFooKtorClient(privatevalurl:String) : FooClient {
privateval client =HttpClient(CIO)
overridefuncall(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 MockServer 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 MockServer tests:

classApp(
privatevalname:String,
privatevalfooApiUrl:String,
privatevalbarApiUrl:String
) {
funexecute() =AppUseCase().execute(
name,
FooKtorClient(fooApiUrl),
BarKtorClient(barApiUrl)
)
}

App test with MockServerExtension

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

@ExtendWith(MockServerExtension::class)
classAppShouldWithOneMockServer {
privateval name ="Ada"
@Test
fun`call foo and bar`(mockServerClient:MockServerClient) {
mockServerClient
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClient
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerUrl ="http://localhost:${mockServerClient.port}"val app =App(name, mockServerUrl, mockServerUrl)
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 two MockServers

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 use two MockServers instead of using MockServerExtension:

@TestInstance(PER_CLASS)
classAppShouldWithTwoMockServers {
privateval name ="Leo"privateval mockServerClientFoo =ClientAndServer()
privateval mockServerClientBar =ClientAndServer()
@Test
fun`call foo and bar`() {
mockServerClientFoo
.`when`(request().withMethod("GET").withPath("/foo").withQueryStringParameter("name", name))
.respond(response().withStatusCode(200).withBody("Hello ${name} I am Foo!"))
mockServerClientBar
.`when`(request().withMethod("GET").withPath("/bar/${name}"))
.respond(response().withStatusCode(200).withBody("Hello $name I am Bar!"))
val mockServerFooUrl ="http://localhost:${mockServerClientFoo.port}"val mockServerBarUrl ="http://localhost:${mockServerClientBar.port}"val app =App(name, mockServerFooUrl, mockServerBarUrl)
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 MockServer Docker

Static stubs

First we will use static stubs configured as json files:

In our docker-compose.yml:

Finally we test the App using Testcontainers JUnit5 extension:

@Testcontainers
@TestInstance(PER_CLASS)
classAppShouldWithMockServerDocker {
companionobject {
privateconstval name ="Ivy"privateconstval fooServiceName ="foo-api"privateconstval fooServicePort =8080privateconstval barServiceName ="bar-api"privateconstval barServicePort =8080privatelateinitvar fooApiHost:Stringprivatevar fooApiPort:Int=0privatelateinitvar barApiHost:Stringprivatevar barApiPort:Int=0val waitForMockServerLiveness = forHttp("/mockserver/status")
.withMethod("PUT")
.forStatusCode(200)
@Container
@JvmStatic
val container =ComposeContainer(File("docker-compose.yml"))
.withLocalCompose(true)
.withExposedService(fooServiceName, fooServicePort, waitForMockServerLiveness)
.withExposedService(barServiceName, barServicePort, waitForMockServerLiveness)
@BeforeAll
@JvmStatic
funbeforeAll() {
fooApiHost = container.getServiceHost(fooServiceName, fooServicePort)
fooApiPort = container.getServicePort(fooServiceName, fooServicePort)
barApiHost = container.getServiceHost(barServiceName, barServicePort)
barApiPort = container.getServicePort(barServiceName, barServicePort)
}
}
@Test
fun`call foo and bar`() {
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()
)
}
}

Dynamic stubs

We can also configure our stubs programmatically using the MockServerClient and connect it to each one of the two MockServer containers:

@Test
fun`call foo an bar with dynamic stubs`() {
val fooApiUrl ="http://${fooApiHost}:${fooApiPort}/dynamic"val barApiUrl ="http://${barApiHost}:${barApiPort}/dynamic"MockServerClient(fooApiHost, fooApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/foo")
.withQueryStringParameter("name", name)
)
.respond(
response()
.withStatusCode(200)
.withBody("Hi $name I am Foo, how are you?")
)
MockServerClient(barApiHost, barApiPort)
.`when`(
request()
.withMethod("GET")
.withPath("/dynamic/bar/$name")
).respond(
response()
.withStatusCode(200)
.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()
)
}

App run with MockServer Docker

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

MockServerDockerRun

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

About

🎭 MockServer Testing

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Used by

Contributors

Languages