WireMock Request Logging

By | October 6, 2020

These are a few short notes on how to log requests received by a WireMock server as well as any mock response generated in connection to a request. I have found this to come in handy when having multiple mappings loaded into WireMock and an incorrect mapping being chosen in the test.
In addition, I will point out a peculiarity with RestAssured, which I have used as test-client in the example.
Please refer to my GitHub repository for the complete example project.

Background

WireMock has the ability to match request and generate responses based on information in files. The following is an example of such a file:

{
  "request" : {
    "urlPathPattern" : "/.*",
    "headers" : {
      "Accept" : {
        "equalTo" : "application/json"
      }
    },
    "method" : "GET"
  },
  "response" : {
    "status" : 200,
    "headers" : {
      "Content-Type": "text/plain"
    },
    "body": "hello json!"
  }
}

The request part of the data is used when matching requests and the response part is used to generate a response in the case of receiving a matching request. In the case of the above mapping file, any GET request with the Accept HTTP header containing application/json will be a matching request.
It is possible to have multiple such mapping files in a project. Sets of mapping files can be stored in different directories and WireMock can be configured to load the mappings in one such directory in preparation of, for example, one set of tests.

Example

The example project contains three WireMock mapping files. The request-matching of each of the files matches Accept=application/json, Accept=text/plain and Accept=application/xml respectively.

The test using WireMock as a mock server and RestAssured as a test client looks like this:

/**
 * Tests using WireMock as a mock server and shows how to log information
 * concerning the requests received by the WireMock server.
 *
 * @author Ivan Krizsan
 */
@SpringBootTest
class WiremockRequestLoggingApplicationTests {
    /* Constant(s): */
    private static final Logger LOGGER = LoggerFactory.getLogger(WiremockRequestLoggingApplicationTests.class);
    protected final static int HTTP_ENDPOINT_PORT = 8123;
    protected final static String WIREMOCK_MAPPINGS_PATH = "mappings/";
    protected final static String URL = "http://localhost:" + HTTP_ENDPOINT_PORT;

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

    /**
     * Tests sending a request to the WireMock server.
     */
    @Test
    void sendRequestToWireMockServerTest() {
        final Response theResponse = RestAssured
            .given()
            .accept("application/xml")
            .when()
            .get(URL);
        theResponse
            .then()
            .statusCode(HttpStatus.SC_OK);
    }

    /**
     * Sets up RestAssured, the test-client, before every test.
     */
    @BeforeEach
    public void setupRestAsssured() {
        RestAssured.reset();
        RestAssured.port = HTTP_ENDPOINT_PORT;
    }

    /**
     * Sets up and configures the WireMock mock server.
     * Responses served by the mock server are selected from a number of mapping
     * files on the classpath.
     * WireMock is also configured to log information about the request and
     * any matching response.
     */
    @BeforeEach
    public void setupWireMock() {
        mWireMockServer = new WireMockServer(HTTP_ENDPOINT_PORT);
        /* Load WireMock mappings from files in directory on classpath. */
        mWireMockServer.loadMappingsUsing(
            new JsonFileMappingsSource(
                new ClasspathFileSource(WIREMOCK_MAPPINGS_PATH)
            ));
        /* Add logging of request and any matched response. */
        mWireMockServer.addMockServiceRequestListener(
            WiremockRequestLoggingApplicationTests::requestReceived);
        mWireMockServer.start();
    }

    /**
     * Stops the WireMock server after each test.
     */
    @AfterEach
    public void stopWireMock() {
        mWireMockServer.stop();
    }

    /**
     * Logs information from supplied WireMock request and response objects.
     *
     * @param inRequest Object containing information from received request.
     * @param inResponse Response object containing data from the selected response.
     * If no response was matched, payload will be null and there will be no response
     * headers.
     */
    protected static void requestReceived(Request inRequest,
        com.github.tomakehurst.wiremock.http.Response inResponse) {
        LOGGER.info("WireMock request at URL: {}", inRequest.getAbsoluteUrl());
        LOGGER.info("WireMock request headers: \n{}", inRequest.getHeaders());
        LOGGER.info("WireMock response body: \n{}", inResponse.getBodyAsString());
        LOGGER.info("WireMock response headers: \n{}", inResponse.getHeaders());
    }
}

Note that:

  • The test class is annotated with @SpringBootTest.
    This is not necessary in order to use WireMock – the example project just happens to be a Spring Boot project. Spring Boot does help me with setting the log level to INFO, which eliminates a lot of debug logs from WireMock and the Apache HTTP client.
  • There is a test method sendRequestToWireMockServerTest.
    Using RestAssured, a GET request is sent with the Accept header having the value application/xml. The response is verified to have the OK (200) status code.
    We’ll come back to change the value of the Accept header later.
  • There is a method named setupWireMock annotated with @BeforeEach.
    The first half of the implementation of the method creates a WireMock server and loads all the mapping files. The last three lines of code adds a request listener to the WireMock server.
  • Finally, there is a method named requestReceived.
    This is the method that is configured as the WireMock request listener. As can be seen, it will log the URL and the headers of the request as well as the response headers and body of the matched response.
    There is additional information both in the request and response objects but I have chosen only to log the above mentioned information in this example.

Running the Test

If I now run the test, it should pass and, among other things, the following will be logged to the console:

2020-10-04 20:07:16.855  INFO 13116 --- [ qtp45515497-26] w.WiremockRequestLoggingApplicationTests : WireMock request at URL: http://localhost:8123/
2020-10-04 20:07:16.855  INFO 13116 --- [ qtp45515497-26] w.WiremockRequestLoggingApplicationTests : WireMock request headers: 
Connection: [keep-alive]
User-Agent: [Apache-HttpClient/4.5.12 (Java/14.0.2)]
Host: [localhost:8123]
Accept-Encoding: [gzip,deflate]
Accept: [application/xml]

2020-10-04 20:07:16.855  INFO 13116 --- [ qtp45515497-26] w.WiremockRequestLoggingApplicationTests : WireMock response body: 
hello xml!
2020-10-04 20:07:16.855  INFO 13116 --- [ qtp45515497-26] w.WiremockRequestLoggingApplicationTests : WireMock response headers: 
Content-Type: [text/plain]
Matched-Stub-Id: [fc737ed6-bf27-4712-8b1f-282205c90f63]

We can see that the request:

  • Was made to the URL http://localhost:8123/
  • Had the following headers set with the associated values:
    Connection: keep-alive
    User-Agent: Apache-HttpClient/4.5.12 (Java/14.0.2)
    Host: localhost:8123
    Accept-Encoding: gzip,deflate
    Accept: application/xml

A response was matched with the following properties:

  • Response body: “hello xml!”
  • Response headers:
    Content-Type: text/plain
    Matched-Stub-Id: ab5815de-3334-4ae6-9402-0370e13998c9


Change the value of the Accept header in the sendRequestToWireMockServerTest test method to test/test and re-run the test. The test will now fail and the following will be logged:

2020-10-05 20:54:46.561  INFO 2291 --- [qtp882838692-26] w.WiremockRequestLoggingApplicationTests : WireMock request at URL: http://localhost:8123/
2020-10-05 20:54:46.562  INFO 2291 --- [qtp882838692-26] w.WiremockRequestLoggingApplicationTests : WireMock request headers: 
Connection: [keep-alive]
User-Agent: [Apache-HttpClient/4.5.12 (Java/14.0.2)]
Host: [localhost:8123]
Accept-Encoding: [gzip,deflate]
Accept: [test/test]

2020-10-05 20:54:46.562  INFO 2291 --- [qtp882838692-26] w.WiremockRequestLoggingApplicationTests : WireMock response body: 
null
2020-10-05 20:54:46.562  INFO 2291 --- [qtp882838692-26] w.WiremockRequestLoggingApplicationTests : WireMock response headers: 
(no headers)

We can see that the request:

  • Was made to the URL http://localhost:8123/
  • Had the following headers set with the associated values:
    Connection: keep-alive
    User-Agent: Apache-HttpClient/4.5.12 (Java/14.0.2)
    Host: localhost:8123
    Accept-Encoding: gzip,deflate
    Accept: test/test

The following response-related information was logged:

  • Response body: null
  • Response headers: (no headers)

Note that there are no response body and no response headers – this means that no response was matched.

RestAssured Peculiarity

Modify the test method sendRequestToWireMockServerTest to look like this:

    @Test
    void sendRequestToWireMockServerTest() {
        final Response theResponse = RestAssured
            .given()
            .accept("application/xml")
            .contentType("application/json")
            .when()
            .get(URL);
        theResponse
            .then()
            .statusCode(HttpStatus.SC_OK);
    }

Yes, I am aware of the fact that GET requests do not need a content type, but please bear with me and run the test one more time. The request information logged will look something like this, depending on the default encoding of your operating system:

2020-10-06 20:26:21.100  INFO 2620 --- [tp1258801872-26] w.WiremockRequestLoggingApplicationTests : WireMock request at URL: http://localhost:8123/
2020-10-06 20:26:21.101  INFO 2620 --- [tp1258801872-26] w.WiremockRequestLoggingApplicationTests : WireMock request headers: 
Connection: [keep-alive]
User-Agent: [Apache-HttpClient/4.5.12 (Java/14.0.2)]
Host: [localhost:8123]
Accept-Encoding: [gzip,deflate]
Accept: [application/xml]
Content-Type: [application/json; charset=UTF-8]

Note that the Content-Type header received by the WireMock server suddenly contains “; charset=UTF-8” in addition to “application/json”. Personally I am not too happy about this. In my opinion, a testing tool like RestAssured should allow me to specify header values that are invalid – I may want to implement a negative test. I do not want the tool to make modifications or additions to the data I specify.

I am aware that there are other ways to have WireMock and RestAssured log request and response information but I wanted that particular information and nothing else. Hope you nevertheless find the WireMock request and response logging as useful as I have.
Happy coding!

Leave a Reply

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