Site icon JVM Advent

Improving quality by mocking APIs with WireMock

While building systems and products today, we often come to situation that our system depend on some 3rd party systems. In most case our system communicate with some API to retrieve some data, that is needed to serve customers. Challenge that we face is how to make sure our systems perform as expected in this complex setup. If we are lucky systems that we interact with, will provide us with test and staging environments, beside production. This environments can be used during development and testing phases. Unfortunately this environments will not always be present, or be up to date or be of performance that we can really on in our day to day work. Due to that one good approach is to use Mocks or look-a-likes for systems that our product will interact with.

WireMock is one of the tools that can help us with challenge above on multiple levels and in different ways.

WireMock and Unit tests

Usually first place where our system need to interact with dependency systems in development process is inside Unit tests. You can be tempted to just call 3rd party system via code from unit test, and this can be done. However in this way you introduce additional challenges and latency to your unit test. This isn’t ideal setup since unit tests should be as fast as possible. Much better option is to somehow mock 3rd party system.

Let us see how we can leverage WireMock for this.

First we need to add WireMock dependency to our project, so let us add this to our pom.xml file

<dependency>
  <groupId>com.github.tomakehurst</groupId>
  <artifactId>wiremock-jre8</artifactId>
  <version>2.31.0</version>
  <scope>test</scope>
</dependency>

After this we can use WireMock in our unit tests. Let us assume that our system makes a GET call to some API on URL “/some/data” and that we expect JSON payload back.

Important thing to point out is that there are differences in using WireMock with Junit4 and Junit5. Let us look at example with Junit4 first.

WireMock and JUnit4

WireMock will start by default on port 8080, in case we want to use different port we need to add code like this

@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);

there are additional settings that we can pass in this way, but for now let us just stay with using port 8089 for WireMock.

Now we need to make a stub (mock) of URL call that we are interested in and tell WireMock what to return in case specific URL is hit. For that we will add this call

stubFor(get("/some/data")
    .willReturn(ok()
        .withHeader("Content-Type", "application/json")
        .withBody("{\"data\":\"some data\"}")));

what we are saying here is, that when there is HTTP GET call for URL http://localhost:8089/some/data, return status OK with header “Content-Type”:”application/json” and JSON payload {“data”:”some data”}.

If needed we can add more stubs/mocks, and once we have all the ones we need, we can write our Unit test code that will call “3rd party API“, that we mocked using WireMock.

We can do one more thing with WireMock. Not only that we can stub/mock API calls that we are interested in, we can also verify if 3rd party API was called from our code and in the way that we expected for it to be called.

To validate if URL http://localhost:8089/some/data was hit with HTTP GET call and appropriate header was used we can use code like this.

verify(getRequestedFor(urlPathEqualTo("/some/data"))
    .withHeader("accept", equalTo("application/json"))
);

in this code, we check if URL was hit with HTTP GET call and if header “accept”:”application/json” was in that call.

Full code for our JUnit test with WireMock in JUnit4 can look some like this

import com.github.tomakehurst.wiremock.junit.WireMockRule;

import org.junit.Rule;
import org.junit.Test;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.junit.Assert.assertTrue;

public class ExampleTest {

    @Rule
    public  WireMockRule wireMockRule = new WireMockRule(8089);

    private HttpResponse makeCallTo(String path) {
        var client = HttpClient.newHttpClient();

        var request = HttpRequest.newBuilder(
                        URI.create("http://localhost:8089"+ path))
                .header("accept", "application/json")
                .build();

        try {
            return client.send(request, HttpResponse.BodyHandlers.ofString());
        } catch (IOException e) {
            return null;
        } catch (InterruptedException e) {
            return null;
        }
    }

    @Test
    public void exampleTest() {
        stubFor(get("/some/data")
                .willReturn(ok()
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"data\":\"some data\"}")));

        HttpResponse response = makeCallTo("/some/data");

        assertTrue ("{\"data\":\"some data\"}".equals(response.body()));

        verify(getRequestedFor(urlPathEqualTo("/some/data"))
                .withHeader("accept", equalTo("application/json"))
        );
    }

}

Both stubing/mocking and verifications can be as complex as we need them to be.

Let us look now how we can do similar thing in JUnit5

WireMock and JUnit5

There is only one thing that we need to do differently in case of JUnit5. We don’t use annotation @Rule to configure WireMock to use different port, instead we need to add annotation @WireMockTest on class it self, so for example to tell WireMock to use port 8089 we would write something like this

@WireMockTest(httpPort = 8089)
public class ExampleTest {

Full code for our JUnit test with WireMock in JUnit5 can look some like this

import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.junit.jupiter.api.Assertions.assertTrue;

@WireMockTest(httpPort = 8089)
public class ExampleTest {

    private HttpResponse makeCallTo(String path) {
        var client = HttpClient.newHttpClient();

        var request = HttpRequest.newBuilder(
                        URI.create("http://localhost:8089"+ path))
                .header("accept", "application/json")
                .build();

        try {
            return client.send(request, HttpResponse.BodyHandlers.ofString());
        } catch (IOException e) {
            return null;
        } catch (InterruptedException e) {
            return null;
        }
    }

    @Test
    public void exampleTest() {
        stubFor(get("/some/data")
                .willReturn(ok()
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"data\":\"some data\"}")));

        HttpResponse response = makeCallTo("/some/data");

        assertTrue ("{\"data\":\"some data\"}".equals(response.body()));

        verify(getRequestedFor(urlPathEqualTo("/some/data"))
                .withHeader("accept", equalTo("application/json"))
        );
    }

}

Standalone WireMock instance

Next place, after unit tests, where we might need to simulate or mock usage of 3rd API is usually some Test Environment. In this way we can simulate more real life situation and validate if all things are working as intended. Here we can easily use standalone option of WireMock. Only thing that we need to do is to download standalone WireMock instance and start it as any java application. Again by default it will start on port 8080, so if we want to change default behavior we need to pass few arguments.

$ java -jar wiremock-jre8-standalone-2.31.0.jar --port 8089

In this case we are telling WireMock to start on port 8089. After we have WireMock up and running there is next question of how to configure it which URL’s it need to mock and in which way. There are multiple ways of doing this, it can be done via Java client, it can be done API calls, also it can be done by using mapping files. Let us look how we can do it by using JSON file.

First of all start standalone instance of WireMock by using above command. Once it starts stop it. Create file by name init_mock.json inside mappings directory that was created once you started stand alone instance of WireMock. Put this data into init_mock.json file

{
  "mappings": [
    {
      "request": {
        "method": "GET",
        "url": "/some/data"
      },
      "response": {
        "status": 200,
        "body" : "{\"data\":\"some data\"}"
      }
    },
    {
      "id": "8c5db8b0-2db4-4ad7-a99f-38c9b00da3f7",
      "request": {
        "url": "/some/datatwo"
      },
      "response": {
        "body": "{\"data\":\"data 2\"}"
      }
    }
  ]
}

and start again WireMock using above command. If we send GET call to URL “/some/data” we should get payload descried in JSON file. Let us try it

$ curl http://localhost:8089/some/data
{"data":"some data"}

Similar to options in Unit test, we can add as much stubs as we want and make them as complex as we want. There is also easy way to send back full files using standalone instance, we only need to put corresponding file into directory __files.

Let us look now into how we can create a lot of good mocks in an easy way.

Recording responses of 3rd party system and playing them back using WireMock

We can use WireMock in standalone setup to record responses of any API and play them back in an easy way. To do so, first we need to start standalone instance of WireMock, so let us use this command for it

$ java -jar wiremock-jre8-standalone-2.31.0.jar --port 8089

after this we need to go to URL http://localhost:8089/__admin/recorder/ using our bowers. In input field we need to enter URL of API that we want to record, for example let us use URL https://pokeapi.co/api/v2/. After this we need to click button record and WireMock will behave like a proxy between our client and API in question.

If we then send request to http://localhost:8089/pokemon/ditto, WireMock will proxy this request to https://pokeapi.co/api/v2/pokemon/ditto, receive response, put it in file in mappings directory and send back response to us. At this point we can stop recording and next time we hit this same URL WireMock will use file from mappings directory to mock response to us.

In this post we just scratched the surface of abilities and options that are available to use with WireMock.

Resources

Author: Vladimir Dejanovic

Founder and leader of AmsterdamJUG.
JavaOne Rock Star, CodeOne Star speaker
Storyteller

Software Architect ,Team Lead and IT Consultant working in industry since 2006 developing high performance software in multiple programming languages and technologies from desktop to mobile and web with high load traffic.

Enjoining developing software mostly in Java and JavaScript, however also wrote fair share of code in Scala, C++, C, PHP, Go, Objective-C, Python, R, Lisp and many others.

Always interested in cool new stuff, Free and Open Source software.

Like giving talks at conferences like JavaOne, Devoxx BE, Devoxx US, Devoxx PL, Devoxx MA, Java Day Istanbul, Java Day Minks, Voxxed Days Bristol, Voxxed Days Bucharest, Voxxed Days Belgrade, Voxxed Days Cluj-Napoca and others

Exit mobile version