logo logo

Simulating Rich Behaviour using Advanced WireMock Features (Part 4)

Simulating using WireMock

In the previous article, we have seen how to use WireMock to create simple stubs for our checkout service endpoint, so that we can test our web shop implementation more effectively, without having to deal with typical challenges that third party dependencies can pose when performing integration and end-to-end tests (as discussed in Chapter 1). In this fourth and final article in this series, I’d like to demonstrate some other useful WireMock features that can help you create even more realistic simulations and thereby better support your testing efforts.

Tutorial Chapters

  1. Increasing Test Efficiency with Simulations & Service Virtualization (Chapter 1)
  2. Isolating Components in Unit Tests with Mockito (Chapter 2)
  3. Getting Started with Service Virtualization using WireMock (Chapter 3)
  4. You’re here →Simulating Rich Behaviour using Advanced WireMock Features (Chapter 4)

Fault Simulations

One of the biggest benefits of using simulations in your integration and end-to-end testing efforts is the ability to simulate fault situations with ease. This is often hard, if not impossible, to achieve when communicating with an actual, real dependency, even more so if you want to recreate those error conditions on demand. When using simulations, however, it’s just another bit of behaviour that you need to configure.

The most straightforward way to simulate an error situation with WireMock is to have your simulation return an HTTP status code that indicates an error:

private void receivePostToCheckOut_RespondWithHttp503() {

    stubFor(
        post(
            urlEqualTo("/checkout")
        )
            .withHeader("Scenario", equalTo("ServiceUnavailable"))
            .willReturn(
                aResponse()
                    .withStatus(503)));
}

In this example, when our application under test performs a POST to /checkout with a header Scenario with value ServiceUnavailable, WireMock will return an HTTP status code 503, which typically indicates that the service invoked, i.e., the checkout service, is unavailable at the time. This behaviour can then be used to check how our web shop application acts when the checkout service is temporarily unavailable.

Note that instead of using a Scenario header with a specific value, you can also use any other request property to match this request to a WireMock response. The important thing is that we can configure WireMock (like any other stubbing or service virtualization solution) to look at a specific property of the incoming request and then decide when to act like the service is broken.

Another example of erroneous behaviour from the checkout dependency is the case when it returns a response that does not meet our expectations, e.g. it contains garbage or behaves unexpectedly in some other way. WireMock offers a number of built-in fault situations that can be used to simulate a malfunctioning dependency. Here’s an example:

private void receivePostToCheckOut_RespondWithGarbage() {

    stubFor(
        post(
            urlEqualTo("/checkout")
        )
            .withHeader("Scenario", equalTo("FaultyBody"))
            .willReturn(
                aResponse()
                    .withFault(Fault.RANDOM_DATA_THEN_CLOSE)));
}

In this example, when a specific POST request is sent to the /checkout endpoint, WireMock responds with some random data, then closes the connection. As the corresponding test included in the GitHub repository demonstrates, this will result in an exception – more specifically a ClientProtocolException in this case – being thrown at the consumer side (i.e., our web shop). This can then be used to check whether our web shop handles these errors appropriately.

Performance characteristics are another aspect of the behaviour of a dependency. For example, during testing, we might also be interested in how our web shop behaves when the checkout process is available, but only responds after some predefined delay. Here’s how to create a WireMock simulation that responds as expected, but only after a fixed delay of 2000 milliseconds:

private void receivePostToCheckOut_ResponseDelayed() {

    stubFor(
        post(
            urlEqualTo("/checkout")
        )
            .withHeader("Scenario", equalTo("Delay"))
            .willReturn(
                aResponse()
                    .withStatus(200)
                    .withFixedDelay(2000)));
}

Other, more realistic options for specifying a delay, for example using a lognormal random or uniformly distributed delay, are also available and can be configured in much the same way as shown in this example.

Stateful Simulations

So far, our simulation did not contain or memorize any form of state, so the way WireMock responds solely depends on characteristics of the input. This implies that it does not matter in what order the requests arrive at the simulation, the response to each of these requests will be the same no matter what. This is not always the case in real systems. Dependencies often have a form of state, for example in the form of sessions or long term data storage (databases). To be able to realistically simulate the behaviour of these stateful dependencies, we need a mechanism that allows us to replicate the notion of state.

To illustrate this concept with an example, let’s assume that the checkout service we’re testing with only performs a checkout when there’s at least one article in the shopping cart. The response to a POST call to /checkout, in this case, does not only depend on the incoming request, but also on whether or not a POST call to a second endpoint /addtocart has been made before. If so, the shopping cart contains at least one item and checkout is successful, if not, checkout fails.

In WireMock, you can model statefulness using a Scenario, which essentially represents a finite state machine for which you define the states and state transitions when you create your simulation. The scenario presented above, for example, can be modeled in WireMock as follows:

private void createStatefulStub_responseDependingOnShoppingCarContents() {

    stubFor(post(urlEqualTo("/checkout")).inScenario("Stateful Checkout")
        .whenScenarioStateIs(Scenario.STARTED)
        .willReturn(aResponse()
            .withStatus(400)
            .withBody("Shopping cart is empty!")
        ));

    stubFor(post(urlEqualTo("/addtocart")).inScenario("Stateful Checkout")
        .whenScenarioStateIs(Scenario.STARTED)
        .willReturn(aResponse()
            .withStatus(200)
            .withBody("Item added to shopping cart.")
        )
        .willSetStateTo("SHOPPING_CART_CONTAINS_ITEMS"));

    stubFor(post(urlEqualTo("/checkout")).inScenario("Stateful Checkout")
        .whenScenarioStateIs("SHOPPING_CART_CONTAINS_ITEMS")
        .willReturn(aResponse()
            .withStatus(200)
            .withBody("Checkout completed successfully.")
        ));
}

Every finite state machine has an entry state, which in WireMock is called Scenario.STARTED. When the stub is in this state and the /checkout endpoint is invoked, it returns (and will keep returning) a response with HTTP status code 400 and a message stating that the shopping cart is empty. Only when a POST to /addtocart is received, a state transition to state SHOPPING_CART_CONTAINS_ITEMS is triggered. When the stub is in this new state, a POST call to /checkout returns a response indicating success. The tests included in the GitHub repository document the expected behaviour of our stateful stub. Again, this is not a feature that is unique to WireMock, all sufficiently mature service virtualization solutions offer a way to simulate stateful behaviour.

This concludes this overview of some of the most useful features that WireMock has to offer. This is by no means a complete overview of all of its features, though. For more information about WireMock, please have a look at the documentation or have a look at this open course workshop.

Once again, you can find all the code examples and associated unit and integration tests in this GitHub repository.

Happy Simulating! 🤗

About the author

Bas Dijkstra

Bas teaches companies around the world how to improve their testing efforts through test automation. He is an independent trainer, consultant and developer living in the Netherlands. When he’s not working he likes to take his bicycle for a ride, go for a run or read a good book.

Leave a Reply

FacebookLinkedInTwitterEmail