Integration Testing: MockServer

The Need to Mock External Resources

When designing comprehensive integration tests to run in a test environment, one common blocker is the need to mock the behavior of an external API that is outside the control of your team.

Sometimes external vendors do not maintain a working test environment for us to test our code against.

MockServer

MockServer is an open source tool that will allow us to easily mock external services.

Example: Using MockServer to Mock Time API with Kotlin App in Docker Container

Demonstration Client

For our example application, we wrote a simple web-server (Hello.kt), using Javalin, that is able to query a public time server API (http://worldtimeapi.org/api/ip) and parse the Unix time from the response object.

package hello
import io.javalin.Javalin
import khttp.get
import java.io.FileInputStream
import java.util.*

fun main(args : Array<String>) {
    val props = Properties()
    props.load(FileInputStream("mockserverdemo.properties"))

    startWebServer(props)
}

private fun startWebServer(props: Properties) {
    val app = Javalin.create().start(8080)
    app.get("/test") { ctx -> ctx.result(getCurrentUnixTime(props.getProperty("test.timeApiUrl")).toString()) }
    app.get("/prod") { ctx -> ctx.result(getCurrentUnixTime(props.getProperty("prod.timeApiUrl")).toString()) }
}

private fun getCurrentUnixTime(queryUri: String): Long {
    return get(queryUri).jsonObject.getLong("unixtime")
}

We need a Dockerfile to specify how to run the sample application in a docker container (source):

FROM java:8
WORKDIR /
ADD target/mockserver-demo-1.0-SNAPSHOT-jar-with-dependencies.jar mockserver-demo-1.0-SNAPSHOT-jar-with-dependencies.jar
ADD mockserverdemo.properties mockserverdemo.properties
EXPOSE 8080
CMD java -jar mockserver-demo-1.0-SNAPSHOT-jar-with-dependencies.jar

Mocking Time API for Test Environment

Imagine that our time API is not available in our test environment. If that was the case, we can set up a simple MockServer instance to replace the time API in our test environment.

First, let’s create a file mockserverconfig.json that will store our mock server behavior. (docs)

[
  {
    "httpRequest": {
      "path": "/api/ip"
    },
    "httpResponse": {
      "body": "{\"abbreviation\":\"CDT\",\"client_ip\":\"99.156.81.3\",\"datetime\":\"2020-08-06T07:28:26.631433-05:00\",\"day_of_week\":4,\"day_of_year\":219,\"dst\":true,\"dst_from\":\"2020-03-08T08:00:00+00:00\",\"dst_offset\":3600,\"dst_until\":\"2020-11-01T07:00:00+00:00\",\"raw_offset\":-21600,\"timezone\":\"America/Chicago\",\"unixtime\":1596716906,\"utc_datetime\":\"2020-08-06T12:28:26.631433+00:00\",\"utc_offset\":\"-05:00\",\"week_number\":32}"
    }
  }
]

Note that the hard-coded JSON response in this file is the same schema as is returned by http://worldtimeapi.org/api/ip.

Configure Docker Compose

Next, we need to create a file docker-compose.yml to use with Docker Compose. This configuration specifies how to run our two containers, the demo client application container (called app) and our mockServer container (mockserver docs).

version: "3.8"
services:
  app:
    build: .
    ports:
      - 8080:8080
  mockServer:
    image: mockserver/mockserver:mockserver-5.11.1
    ports:
      - 1080:1080
    environment:
      MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties
      MOCKSERVER_INITIALIZATION_JSON_PATH: /config/mockserverconfig.json
    volumes:
      - type: bind
        source: .
        target: /config

Docker will download a pre-built MockServer container and use our properties file mockserverconfig.json.

A logical future enhancement to this demonstration would be to conditionally load the MockServer container (only load it in the test environment, not in production).

Add Properties File

You may have noticed we have two properties specified in our client (Hello.kt), test.timeApiUrl and prod.timeApiUrl:

  • prod.timeApiUrl is our real, functioning time api endpoint on the internet
  • test.timeApiUrl is our static mock endpoint provided by the MockServer container

We can place them in a single file, mockserverdemo.properties, to simplify this demonstration:

prod.timeApiUrl=http://worldtimeapi.org/api/ip
test.timeApiUrl=http://host.docker.internal:1080/api/ip

Running and Testing

Finally, let’s build and run our application using the following command:

mvn clean package && docker-compose build && docker-compose up

Now, using the endpoints we configured, we can test our different time providers (the real one and the mock one).

If we call:

GET http://localhost:8080/prod

We will get a dynamically increasing value. This is because we are hitting the real time-api endpoint, and time is elapsing.

If we call:

GET http://localhost:8080/test

We get a constant value, as we are hitting our MockServer instance which is configured to return a JSON constant.

Full Source Code for Demo

Useful Links and Resources Used

Leave a Reply

Your email address will not be published. Required fields are marked *