Mocking HTTP Services with WireMock – Part 1

By | March 2, 2018

I have been busy as of lately but feel that I really should squeeze in an article on mocking HTTP services after having found another nice tool that has been on my wishlist for some time.
In this first article I will show some examples of using WireMock that are of particular use to me. In the second part I will show how to use WireMock as a HTTPS mock service and REST Assured as a HTTPS client with and without mutual authentication.

The completed code of the example can be found on GitHub.

Setting Up an Example Project

As always, in my opinion, the best way to learn new things is to get down to the nitty-gritty with some code examples.
I’ll use Spring Boot for my example and a skeleton Maven project can be downloaded from Spring Initializr using this URL.
I am aware of the Spring Cloud Contract WireMock dependency that can be added when creating a Spring Boot project but I want this article to be applicable to other types of projects as well, so I have deliberately chosen to not to use it.

Add the following dependencies to the example project’s pom.xml file:

        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock-standalone</artifactId>
            <version>2.15.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>3.0.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

The first dependency is the WireMock dependency that contains all its dependencies in one JAR-file. This is the recommended dependency to use with SpringBoot projects and, as far as I understand, the easiest way to assure that all dependencies of WireMock are satisfied and that there will be no conflicts between the version of a dependency used by WireMock and another version of the same dependency used by your project or some other dependency of your project.

The next dependency is REST Assured, which I have already introduced.

Finally there is the JUnit 4 dependency. The Spring Boot parent pom-file include these JUnit dependency in its dependency-management section so no version number is needed.

Example Project Log Configuration

In the example project, I’ve used the following Logback configuration in a file named “logback-test.xml” located in src/test/resources:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx</pattern>
            <immediateFlush>false</immediateFlush>
        </encoder>
    </appender>

    <logger name="org.wiremock" level="INFO" />
    <logger name="se.ivankrizsan" level="DEBUG" />

    <root level="WARN">
        <appender-ref ref="CONSOLE" />
    </root>

</configuration>

Response Template

In one of the examples I show how WireMock can be configured to return responses created from templates. Create the template that later will be used in the example as follows.

  • Create the directory hierarchy __files/se/ivankrizsan/wiremocktest in the src/test/resources/ directory.
    Note that there are two underscore characters preceding “files”.
  • In the wiremocktest directory, create a file named “soap-response.xml” without quotes that has the following contents:
<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <ConversionRateResponse xmlns="http://www.webserviceX.NET/">
            <ConversionRateResult>{{request.headers.exchangerate}}</ConversionRateResult>
        </ConversionRateResponse>
    </soap:Body>
</soap:Envelope>

Note that the <ConversionRateResult> element contains a placeholder. Prior to returning the response, WireMock will replace the placeholder with the value of the HTTP header named “exchangerate” from the request .

Abstract Base-Class for Example Tests

In order to minimize repetition and have code that is easier to read, I’ve created the following abstract base-class for the tests:

package se.ivankrizsan.wiremocktest;

import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract base-class for tests containing common constants and methods.
 *
 * @author Ivan Krizsan
 */
public abstract class AbstractTestBase {
    /* Constant(s): */
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTestBase.class);

    protected final static int HTTP_ENDPOINT_PORT = 8123;
    protected final static int HTTPS_ENDPOINT_PORT = 8443;
    protected static final String BASE_PATH = "/wiremocktest/hello";
    protected static final String BASE_HTTP_URL =
        "http://localhost:" + HTTP_ENDPOINT_PORT + BASE_PATH;
    protected static final String BASE_HTTPS_URL =
        "https://localhost:" + HTTPS_ENDPOINT_PORT + BASE_PATH;
    protected static final int DEFAULT_TIMEOUT = 5000;
    /* Client keystore and truststore. Self-signed. */
    protected static final String CLIENT_KEYSTORE_PATH = "client/client_keystore.jks";
    protected static final String CLIENT_KEYSTORE_PASSWORD = "secret";
    protected static final String CLIENT_TRUSTSTORE_PATH = "client/client_cacerts.jks";
    protected static final String CLIENT_TRUSTSTORE_PASSWORD = "secret";
    /* Server keystore and truststore. Self-signed. */
    protected static final String SERVER_KEYSTORE_PATH = "client/server/server_keystore.jks";
    protected static final String SERVER_KEYSTORE_PASSWORD = "secret";
    protected static final String SERVER_TRUSTSTORE_PATH = "client/server/server_cacerts.jks";
    protected static final String SERVER_TRUSTSTORE_PASSWORD = "secret";

    /**
     * Initializes REST Assured for plain HTTP communication. To be called before each test.
     */
    protected void initializeRestAssuredHttp() {
        RestAssured.reset();
        RestAssured.port = HTTP_ENDPOINT_PORT;
    }

    /**
     * Logs the HTTP status, the HTTP headers and the body from the supplied response.
     *
     * @param inResponseEntity Response for which to log information.
     */
    protected void logResponseStatusHeadersAndBody(final Response inResponseEntity) {
        LOGGER.info("Response status: {}", inResponseEntity.getStatusCode());
        LOGGER.info("Response headers: {}", inResponseEntity.getHeaders());
        LOGGER.info("Response body: " + inResponseEntity.getBody().asString());
    }
}

Basic Use of WireMock

In this section some examples of how WireMock behaves in some basic cases:

  • Receiving an expected request.
  • Receiving a request that does not match the expected request.
  • Simulate long request processing time/slow response.
  • Creating responses from templates.

Before we start looking at the examples, create the class WireMockJUnit4Tests class containing the following code and constants:

package se.ivankrizsan.wiremocktest;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static io.restassured.config.XmlConfig.xmlConfig;
import static org.hamcrest.Matchers.*;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.hamcrest.core.StringContains;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import java.util.List;

/**
 * Examples on how to use WireMock with JUnit4.
 * This class creates and destroys the WireMock server programmatically. It also
 * programmatically retrieves unmatched requests from the WireMock server and
 * logs these as errors.
 * An alternative is to use the WireMock server as a JUnit @Rule, please refer to
 * {@link WireMockJUnit4WithRuleTests} for an example.
 *
 * @author Ivan Krizsan
 */
public class WireMockJUnit4Tests extends AbstractTestBase {
    /* Constant(s): */
    private static final Logger LOGGER = LoggerFactory.getLogger(WireMockJUnit4Tests.class);
    protected static final String TESTFILES_BASE =
        "se/ivankrizsan/wiremocktest/";
    public static final String EXCHANGE_RATE = "4.123";

    /* Instance variable(s): */
    protected WireMockServer mWireMockServer;

    /**
     * Performs preparations before each test.
     */
    @Before
    public void setup() {
        initializeRestAssuredHttp();

        /*
         * Create the WireMock server to be used by a test.
         * This also ensures that the records of received requests kept by the WireMock
         * server and expected scenarios etc are cleared prior to each test.
         * An alternative is to create the WireMock server once before all the tests in
         * a test-class and call {@code resetAll} before each test.
         */
        mWireMockServer = new WireMockServer(HTTP_ENDPOINT_PORT);
        mWireMockServer.start();
    }

    /**
     * Performs cleanup after each test.
     */
    @After
    public void tearDown() {
        /* Stop the WireMock server. */
        mWireMockServer.stop();

        /*
         * Find all requests that were expected by the WireMock server but that were
         * not matched by any request actually made to the server.
         * Logs any such requests as errors.
         */
        final List<LoggedRequest> theUnmatchedRequests = mWireMockServer.findAllUnmatchedRequests();
        if (!theUnmatchedRequests.isEmpty()) {
            LOGGER.error("Unmatched requests: {}", theUnmatchedRequests);
        }
    }
}

Note that:

  • In the setup method, REST Assured is initialized for plain HTTP communication and a WireMock server is created and started programmatically.
    Being annotated with the JUnit 4 annotation @Before, this method will run once before each test in the class.
  • In the tearDown method, the WireMock server is stopped and any requests that the WireMock server received but was not expecting are logged.

Matching Request

The first example shows how to configure WireMock to expect a request with certain properties and what happens when WireMock receives a request that does match the expectations.

In the WireMockJUnit4Tests class create the following method:

    /**
     * Test sending a HTTP request to the mock server that matches the request that
     * the mock server expects.
     *
     * Expected result: A response with an expected HTTP status (418) and a response body
     * containing a greeting should be returned.
     */
    @Test
    public void matchingRequestTest() {
        /*
         * Setup test HTTP mock as to expect one request to /wiremock/test with an Accept
         * header that has the value "text/plain".
         * When having received such a request, the mock will return a response with
         * the HTTP status 418 and the header Content-Type with the value "text/plain".
         * The body of the response will be a string containing a greeting.
         */
        mWireMockServer.stubFor(
            get(urlEqualTo(BASE_PATH))
                .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.TEXT_PLAIN_VALUE))
                .willReturn(
                    aResponse()
                        .withStatus(HttpStatus.I_AM_A_TEAPOT.value())
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
                        .withBody("Hello client, this is the response body.")
                )
        );

        /* Send the test-request and save the response so we can log information from it. */
        final Response theResponse = RestAssured
            .given()
            .contentType(ContentType.TEXT)
            .accept(ContentType.TEXT)
            .when()
            .get(BASE_HTTP_URL);
        theResponse
            .then()
            .statusCode(HttpStatus.I_AM_A_TEAPOT.value())
            .contentType(ContentType.TEXT);

        /* Log information from the response for the sake of this being an example. */
        logResponseStatusHeadersAndBody(theResponse);

        /* Verify the result. */
        Assert.assertThat("Response message should contain greeting",
            theResponse.asString(), new StringContains("Hello"));
    }

Note that:

  • First in the method, the expected request is configured on the WireMock server.
    The properties that the expected request has are:
    The request is made to the /wiremocktest/hello path of the web application.
    An Accept HTTP header with the value text/plain.
  • The stubFor method is invoked on the WireMock server object.
    When creating a WireMock server programmatically, the stubFor method is to be invoked on the WireMock server object. When a JUnit Rule is used to create and start the WireMock server, the stubFor method is a static method.
  • As part of the configuration of the WireMock server, the response that is to be returned when having received a request matching the expectations is configured.
    The response will have the HTTP status 418, contain a HTTP header Content-Type with the value text/plain and have a response body containing the greeting “Hello client, this is the response body.”.
  • Next the REST Assured client sends a request and verifies the response.
  • The request that the REST Assured client sends has the following properties:
    Contains the HTTP header Content-Type with the value text/plain.
    Contains the HTTP header Accept with the value text/plain.
    Is to be sent to the /wiremocktest/hello path of the web application.
  • The REST Assured client expects a response with the following properties:
    HTTP status 418 and Content-Type text/plain.
  • After having sent the request and made the above verifications of the response, the HTTP status and headers of the response are logged to the console.
    This is just so that we have something to look at after having run the example.
  • Finally, the response body is verified to contain the word “Hello”.

If we now run the example/test, it should pass and you should see the following rows in the console output:

20:00:33.273 [INFO ] s.i.w.AbstractTestBase - Response status: 418
20:00:33.275 [INFO ] s.i.w.AbstractTestBase - Response headers: Content-Type=text/plain
Transfer-Encoding=chunked
Server=Jetty(9.2.z-SNAPSHOT)
20:00:33.282 [INFO ] s.i.w.AbstractTestBase - Response body: Hello client, this is the response body.

No surprises here – the test passed and everything is as expected.

Mismatching Request with Programmatically Created WireMock Server

Having had a look at a request that matched what WireMock was expected, we must of course investigate what happens when WireMock receives a request that does not match its expectations. This first mismatch-example shows the behaviour of WireMock when the WireMock server was created programmatically.
In the WireMockJUnit4Tests class create the following method:

    /**
     * Test sending a HTTP request to the mock server that does not match the request
     * that the mock server expects.
     * This example shows how WireMock behaves when the WireMock server is created
     * programmatically as opposed to using a JUnit 4 Rule to create the server.
     *
     * Expected result: A response with HTTP status 404 should be returned and a payload
     * explaining the difference(s) between the actual and expected request.
     */
    @Test
    public void mismatchingRequestTest() {
        /*
         * Test HTTP mock expects one request to /wiremock/test with an Accept header
         * that has the value "application/json".
         * When having received such a request, it will return a response with
         * the HTTP status 200 and the header Content-Type with the value "text/plain".
         * The body of the response will be a string containing a greeting.
         */
        mWireMockServer.stubFor(
            get(urlEqualTo(BASE_PATH))
                .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON_VALUE))
                .willReturn(
                    aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
                        .withBody("Hello World, we have a Houston!")
                )
        );

        /*
         * Send the test-request and save the response so we can log information from it.
         * No verification of the response is undertaken, as a content-type that the mock
         * server is not expecting is deliberately sent and the .
         * This is done in order to examine the response in cases like this.
         */
        final Response theResponse = RestAssured
            .given()
            .contentType(ContentType.XML)
            .accept(ContentType.TEXT)
            .when()
            .get(BASE_HTTP_URL);

        /*
         * In a normal test the HTTP status of the response would be expected to be OK (200),
         * but since the content-type of the request is deliberately chosen not to match,
         * a NOT-FOUND (404) will be returned.
         */
        Assert.assertEquals("The HTTP status should be not-found, since "
            + "a request was sent that was not expected by WireMock",
            HttpStatus.NOT_FOUND.value(), theResponse.getStatusCode());

        /* Log information from the response for the sake of the example. */
        logResponseStatusHeadersAndBody(theResponse);
    }

Note that:

  • The WireMock server is configured to expect a request with the following properties:
    Request contains the Accept HTTP header with the value application/json.
    The request is made to the /wiremocktest/hello path of the web application.
  • When having received a matching request, WireMock is configured to return a response with the following properties:
    Content-Type text/plain. HTTP status 200 (OK). Response body “Hello World, we have a Houston!”.
  • Next a request is sent that does not match the expectations configured on WireMock.
    Note that no verification of the response is made with REST Assured.
  • The HTTP response status is verified to be 404 (not-found).
    This is the status that a programmatically created WireMock server will return when having received a request that does not match expectations.
  • The HTTP status and headers of the response are logged to the console.

If we run this example, it should also pass but the log in the console will indicate that there was a request that did not match expectations:

20:23:25.763 [ERROR] WireMock - 
                                               Request was not matched
                                               =======================

-----------------------------------------------------------------------------------------------------------------------
| Closest stub                                             | Request                                                  |
-----------------------------------------------------------------------------------------------------------------------
                                                           |
GET                                                        | GET
/wiremocktest/hello                                        | /wiremocktest/hello
                                                           |
Accept: application/json                                   | Accept: text/plain                                  <<<<< Header does not match
                                                           |
                                                           |
-----------------------------------------------------------------------------------------------------------------------

20:23:25.814 [INFO ] s.i.w.AbstractTestBase - Response status: 404
20:23:25.816 [INFO ] s.i.w.AbstractTestBase - Response headers: Content-Type=text/plain
Transfer-Encoding=chunked
Server=Jetty(9.2.z-SNAPSHOT)
20:23:25.823 [INFO ] s.i.w.AbstractTestBase - Response body: 
                                               Request was not matched
                                               =======================

-----------------------------------------------------------------------------------------------------------------------
| Closest stub                                             | Request                                                  |
-----------------------------------------------------------------------------------------------------------------------
                                                           |
GET                                                        | GET
/wiremocktest/hello                                        | /wiremocktest/hello
                                                           |
Accept: application/json                                   | Accept: text/plain                                  <<<<< Header does not match
                                                           |
                                                           |
-----------------------------------------------------------------------------------------------------------------------

20:23:25.831 [WARN ] w.o.e.j.u.t.QueuedThreadPool - qtp702025003{STOPPING,8<=8<=10,i=0,q=3} Couldn't stop Thread[qtp702025003-13,5,]
20:23:25.831 [WARN ] w.o.e.j.u.t.QueuedThreadPool - qtp702025003{STOPPING,8<=8<=10,i=0,q=3} Couldn't stop Thread[qtp702025003-18,5,]
20:23:25.832 [ERROR] s.i.w.WireMockJUnit4Tests - Unmatched requests: [{
  "url" : "/wiremocktest/hello",
  "absoluteUrl" : "http://localhost:8123/wiremocktest/hello",
  "method" : "GET",
  "clientIp" : "127.0.0.1",
  "headers" : {
    "Accept" : "text/plain",
    "Connection" : "keep-alive",
    "User-Agent" : "Apache-HttpClient/4.5.5 (Java/1.8.0_152)",
    "Host" : "localhost:8123",
    "Accept-Encoding" : "gzip,deflate",
    "Content-Length" : "0",
    "Content-Type" : "application/xml; charset=ISO-8859-1"
  },
  "cookies" : { },
  "browserProxyRequest" : false,
  "loggedDate" : 1519932205748,
  "bodyAsBase64" : "",
  "body" : "",
  "loggedDateString" : "2018-03-01T19:23:25Z",
  "queryParams" : { }
}]

Note that:

  • The message that the request was not matched is logged twice.
    This is due to the fact that the message is returned in the body of the response too.
  • We can see that the cause of the mismatch was the value of the Accept HTTP header.
    It was expected to have the value application/json but contained text/plain.
  • The response HTTP status is 404.
    Again, this is the expected response HTTP status when a mismatching request is received.
  • Finally, we see that the code in the tearDown method logs the unmatched request and all of its details in even greater depth.

Mismatching Request with WireMock Server Created by JUnit Rule

As a contrast to the example above showing how a programmatically created WireMock server behaves when receiving a mismatching request, this example will show how a WireMock server created by a JUnit Rule behaves under the same circumstances.

The example is implemented in a separate class named WiremockJUnit4WithRuleTests that looks like this:

package se.ivankrizsan.wiremocktest;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

import com.github.tomakehurst.wiremock.client.VerificationException;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

/**
 * Examples on how to use WireMock with JUnit4.
 * This class uses a JUnit @Rule to create and destroy the WireMock server.
 *
 * @author Ivan Krizsan
 */
public class WireMockJUnit4WithRuleTests extends AbstractTestBase {
    /* Constant(s): */

    /* Instance variable(s): */
    @Rule
    public WireMockRule mWireMockRule = new WireMockRule(HTTP_ENDPOINT_PORT);

    /**
     * Performs preparations before each test.
     */
    @Before
    public void setup() {
        initializeRestAssuredHttp();
    }

    /**
     * Test sending a HTTP request to the mock server that does not match the request
     * that the mock server expects.
     * This example shows how WireMock behaves when using a JUnit rule to create the
     * WireMock server.
     *
     * Expected result: A response with the HTTP status not-found should be received and
     * a {@code VerificationException} should be thrown when the JUnit rule is evaluated,
     * after completion of the test.
     */
    @Test
    public void mismatchingRequestTest() {
        /*
         * Test HTTP mock expects one request to /wiremock/test with an Accept header
         * that has the value "application/json".
         * When having received such a request, it will return a response with
         * the HTTP status 200 and the header Content-Type with the value "text/plain".
         * The body of the response will be a string containing a greeting.
         */
        stubFor(
            get(urlEqualTo(BASE_PATH))
                .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_JSON_VALUE))
                .willReturn(
                    aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
                        .withBody("Hello World, we have a Houston!")
                )
        );

        /*
         * Send the test-request and save the response so we can log information from it.
         * No verification of the response is undertaken, as a content-type that the mock
         * server is not expecting is deliberately sent and the .
         * This is done in order to examine the response in cases like this.
         */
        final Response theResponse = RestAssured
            .given()
            .contentType(ContentType.XML)
            .accept(ContentType.TEXT)
            .when()
            .get(BASE_HTTP_URL);

        /*
         * In a normal test the HTTP status of the response would be expected to be OK (200),
         * but since the content-type of the request is deliberately chosen not to match,
         * a NOT-FOUND (404) will be returned.
         */
        Assert.assertEquals("The HTTP status should be not-found, since "
                + "a request was sent that was not expected by WireMock",
            HttpStatus.NOT_FOUND.value(), theResponse.getStatusCode());

        /* Log information from the response for the sake of this being an example. */
        logResponseStatusHeadersAndBody(theResponse);
    }
}

Note that:

  • There is a public instance variable named mWireMockRule of the type WireMockRule and annotated with the @Rule annotation.
    This is the JUnit rule that will setup and start the WireMock server before each test method and also stop it after each test method.
  • As in the previous example/test class there is a setup method that initializes REST Assured.
  • In the method mismatchingRequestTest the WireMock server is set up to expect a request with certain properties.
    Note that the stubFor method is invoked statically.
    When a JUnit Rule is used to create and start the WireMock server, the stubFor method is a static method. When creating a WireMock server programmatically, the stubFor method is to be invoked on the WireMock server object, as we have seen earlier.
  • A request is sent which have the Content-Type application/xml, as opposed to the expected content type text/plain.
  • An assertion is made that the HTTP response status is 404.
  • HTTP response status, headers and body are logged to the console.

If we run the test, it will fail and we will see the following in the console:

21:11:29.937 [ERROR] WireMock - 
                                               Request was not matched
                                               =======================

-----------------------------------------------------------------------------------------------------------------------
| Closest stub                                             | Request                                                  |
-----------------------------------------------------------------------------------------------------------------------
                                                           |
GET                                                        | GET
/wiremocktest/hello                                        | /wiremocktest/hello
                                                           |
Accept: application/json                                   | Accept: text/plain                                  <<<<< Header does not match
                                                           |
                                                           |
-----------------------------------------------------------------------------------------------------------------------

21:11:29.978 [INFO ] s.i.w.AbstractTestBase - Response status: 404
21:11:29.980 [INFO ] s.i.w.AbstractTestBase - Response headers: Content-Type=text/plain
Transfer-Encoding=chunked
Server=Jetty(9.2.z-SNAPSHOT)
21:11:29.986 [INFO ] s.i.w.AbstractTestBase - Response body: 
                                               Request was not matched
                                               =======================

-----------------------------------------------------------------------------------------------------------------------
| Closest stub                                             | Request                                                  |
-----------------------------------------------------------------------------------------------------------------------
                                                           |
GET                                                        | GET
/wiremocktest/hello                                        | /wiremocktest/hello
                                                           |
Accept: application/json                                   | Accept: text/plain                                  <<<<< Header does not match
                                                           |
                                                           |
-----------------------------------------------------------------------------------------------------------------------


com.github.tomakehurst.wiremock.client.VerificationException: A request was unmatched by any stub mapping. Closest stub mapping was:   <Click to see difference>

The main difference when compared to the previous example is that the test fails. In the console log, we see the HTTP response status, headers and body being logged so the test-failure is after the completion of the test method.
In IntelliJ IDEA, there is a clickable link in the console output that, if you click on it, shows a Comparison window detailing the differences between the request expected by the WireMock server and the actual request received. I recall having read that a similar feature is available in Eclipse.

Slow Responses

Another scenario that I frequently encounter when testing is simulating a slow service. As a spoiler, I will already now reveal that it is very easy to mock a slow HTTP service with WireMock. In addition, it is just as easy to verify that a request has taken a certain time with REST Assured.
Back in the WireMockJUnit4Tests class, implement the following method:

    /**
     * Tests sending a request to the mock server to which the mock server will respond
     * with a 10 second delay.
     *
     * Expected result: A response containing a greeting should be received after
     * a delay exceeding a certain period of time.
     */
    @Test
    public void slowResponseTest() {
        /*
         * Setup test HTTP mock as to expect one request to /wiremock/test with an Accept
         * header that has the value "text/plain".
         * When having received such a request, the mock will return a response with
         * the HTTP status 200 and the header Content-Type with the value "text/plain".
         * The body of the response will be a string containing a greeting.
         * There will be a delay of 10 seconds for each request to the mock service.
         */
        mWireMockServer.stubFor(
            get(urlEqualTo(BASE_PATH))
                .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.TEXT_PLAIN_VALUE))
                .willReturn(
                    aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
                        .withBody("Hello client, this is the response body.")
                        .withFixedDelay(DEFAULT_TIMEOUT * 2)
                )
        );

        /* Send the test-request. */
        RestAssured
            .given()
            .contentType(ContentType.TEXT)
            .accept(ContentType.TEXT)
            .when()
            .get(BASE_HTTP_URL)
            .then()
            .time(greaterThan((long) DEFAULT_TIMEOUT))
            .statusCode(HttpStatus.OK.value())
            .contentType(ContentType.TEXT);
    }

Note that:

  • The WireMock server is configured to expect a request with the following properties:
    Request contains the Accept HTTP header with the value text/plain.
    The request is made to the /wiremocktest/hello path of the web application.
  • When having received a matching request, WireMock is configured to return a response after a 10 second delay with the following properties:
    Content-Type text/plain. HTTP status 200 (OK). Response body “Hello client, this is the response body.”.
  • Using REST Assured, a request that matches what WireMock expects is sent.
  • REST Assured also verifies that the time it took to receive a response is longer that five seconds.

If we run the example/test, it will eventually pass. In IntelliJ IDEA the time consumed by a test method is displayed next to the method name in the Run view and there I can see that the test took approximately 11.5 seconds before completing.

Response Templates

I have written an article earlier about creating request and responses for testing purposes using Thymeleaf, so naturally I wanted to know if WireMock has support for creating responses from templates.
It turns out that WireMock indeed is able to create mock responses from templates. For this purpose it uses Handlebars as its templating engine. The following example from the WireMockJUnit4Tests class show how to return XML responses created from the template we prepared earlier.

    /**
     * Tests sending a request to the mock server where the mock server will respond
     * with the contents of a template-file in which values are inserted.
     *
     * Expected result: A response containing an XML fragment should be received
     * with an element containing an exchange rate.
     */
    @Test
    public void responseTemplateTest() {
        /* Stop the default WireMock server, since a custom one is needed for this test. */
        mWireMockServer.stop();

        /*
         * Create the response transformer that will insert values into the response
         * templates. Note that the transformer is not global, that is it is not automatically
         * applied to all responses from the WireMock server.
         */
        final ResponseTemplateTransformer theTemplateTransformer =
            new ResponseTemplateTransformer(false);
        final String theTemplateTransformerName = theTemplateTransformer.getName();
        mWireMockServer = new WireMockServer(
            WireMockConfiguration
                .options()
                .port(HTTP_ENDPOINT_PORT)
                .extensions(theTemplateTransformer));
        mWireMockServer.start();
        /*
         * Setup test HTTP mock as to expect one request to /wiremock/test with an Accept
         * header that has the value "application/xml".
         * When having received such a request, the mock will return a response with
         * the HTTP status 200 and the header Content-Type with the value "text/plain".
         * The body of the response will be a the contents of a file located at
         * "src/test/resources/__files/se/ivankrizsan/wiremocktest/soap-response.xml"
         * in the project.
         * The value of the request HTTP header "exchangerate" will be inserted into
         * the response created from the template-file.
         */
        mWireMockServer.stubFor(
            get(urlEqualTo(BASE_PATH))
                .withHeader(HttpHeaders.ACCEPT, equalTo(MediaType.APPLICATION_XML_VALUE))
                .willReturn(
                    aResponse()
                        .withBodyFile(TESTFILES_BASE + "soap-response.xml")
                        .withTransformers(theTemplateTransformerName)
                        .withStatus(HttpStatus.OK.value())
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
                )
        );

        /*
         * Send test-request to the mock server.
         * Note that XML namespace awareness is switched off in order to avoid having
         * to specify namespace mappings.
         * XPath expressions will work against the local names of elements and ignore
         * any namespaces used in the XML.
         */
        final Response theResponse = RestAssured
            .given()
            .config(RestAssured.config().xmlConfig(xmlConfig().with().namespaceAware(false)))
            .contentType(ContentType.XML)
            .accept(MediaType.APPLICATION_XML_VALUE)
            .header("exchangerate", EXCHANGE_RATE)
            .when()
            .get(BASE_HTTP_URL);

        /*
         * Verify that the response is OK, is of the XML type and contains the exchange rate
         * in the expected element in the XML.
         */
        theResponse
            .then()
            .statusCode(HttpStatus.OK.value())
            .contentType(ContentType.XML)
            .body(
                hasXPath(
                    "/Envelope/Body/ConversionRateResponse/ConversionRateResult",
                    containsString(EXCHANGE_RATE))
            );

        logResponseStatusHeadersAndBody(theResponse);
    }

Note that:

  • The WireMock server is stopped first in the test-method.
    This may seem odd but it is due to the fact that the WireMock server created in preparation for each test-method cannot be used in this test.
  • A response template transformer is created and its name is saved.
    This transformer is what enables response templating with WireMock.
    The boolean parameter supplied to the constructor indicated whether the transformer is to be applied automatically when creating all responses from the WireMock server. The reason for saving the name of the transformer is that we need to use the name to tell WireMock to use this particular transformer when creating responses later.
  • The WireMock server is created and started.
    Note the use of WireMockConfiguration to supply a more advanced configuration to WireMock and the use of the extensions method to supply the transformer created earlier.
  • The WireMock server is configured which request to expect.
    This is a request to the usual HTTP path with the Accept header having the value application/xml.
  • The WireMock server is configured which response to return when having received the expected request.
    Notice that the response body is to be read from a file, as specified by the withBodyFile method and that there is a call to a method named withTransformers taking the name of the response transformer we created earlier as a parameter.
    The astute reader concludes that no response transformer is necessary if you only want to serve responses with bodies created from statical file contents.
  • A request is sent to the service.
    The request is a request that WireMock expects and, in addition, there is a HTTP header named “exchangerate” that has the value 4.123.
    When we created the response template, this was the HTTP header value that is to be inserted into the XML fragment.
  • The response is verified.
    In addition to the type of verification we have seen earlier, the response is also verified to contain the value 4.123 at a location in the XML specified by an XPath expression.
  • Finally the response is logged to the console.

If we now run the test we’ll see this, among other things, in the console:

07:01:15.829 [INFO ] s.i.w.AbstractTestBase - Response status: 200
07:01:15.829 [INFO ] s.i.w.AbstractTestBase - Response headers: Content-Type=application/xml
Content-Encoding=gzip
Vary=Accept-Encoding, User-Agent
Transfer-Encoding=chunked
Server=Jetty(9.2.z-SNAPSHOT)
07:01:15.829 [INFO ] s.i.w.AbstractTestBase - Response body: <soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <ConversionRateResponse xmlns="http://www.webserviceX.NET/">
            <ConversionRateResult>4.123</ConversionRateResult>
        </ConversionRateResponse>
    </soap:Body>
</soap:Envelope>

This concludes part one in the two-part series on WireMock. In the next part I will show testing with HTTPS; both with and without mutual authentication.
Until then, happy coding!

 

Leave a Reply

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