Wiremock, JsonPath and the body of your http request

By Will Braynen

 A How To

This week I learned something new: how to have the WireMock server, run as a standalone process, return a different JSON response depending on what's in the body of the HTTP request.  So if you read this, then you will be smarter than I was a week ago.  It goes like this.

Two things were new to me.  The first was that I did not know that WireMock can respond differently given the body of the request.  I thought that it could only tell requests apart by the URI, meaning that either endpoints had to differ or the IDs of resources (or query strings).  The second thing I did not know was that JsonPath existed.  JsonPath is basically XPath for JSON.  (XPath is a tool that does the same sort of thing but with XML documents.)  The combination of JsonPath and WireMock is pretty neat.  And it can be equally handy on the backend (e.g. microservices) and the frontend (e.g. iOS).

JSONs are collections of key-value pairs (where, given that a json can be nested, a value can be another collection of key-value pairs).  Because jsons are all about keys and values, this post is broken up into two sections: 1. By Key, and 2. By Key and Value.

 

By key

Let's say you want to send (by PUT) the following json in the body of your http request to your microservice:

{ "name": "Dave" }

And, because you are mocking this microservice, let's say you want your mock server to return a 200 if the key "name" is present in your payload (happy-path scenario), but receive a 400 if the key "name" is missing (rainy-day scenario).  How do you mock this?  The first insight is that you do not need to return a 400 whenever "name" is missing.  You only need one case to test how well your client application handles a 400 rainy-day scenario.  For your purposes, it should be enough to have your mock server respond with a 400 when its receives "blah" instead of "name".  But "grah" and "flah" and all other possible value other than "name" you don't care about; after all, you are testing your client application's reaction to a 400, not the mock server. So, here is a json to send in the body of our http request for our rainy-day scenario:

{ "blah": "Dave" }

So what we want is this:

Screen Shot 2017-06-22 at 6.36.00 PM.png

Now that we have a spec for our mock, let's implement it.  The wiremock mapping (to be placed, as a json file, in a `mappings` folder) that maps the happy-path http request above to a 200 http response would look as follows:

{
  "request": {
      "method": "PUT",
      "urlPath": "/minions",
      "bodyPatterns": [ {
        "matchesJsonPath": "$['name']"
      }]
  },
  "response": {
      "status": 200
   }
}

Note that `$['name'] can also be written as `$.name`, which is more common.  JsonPath syntax supports both.

Let's name the above mapping file "200.json".  Next, we would need a mapping for the 400 rainy-day scenario.  Let's call this one "400.json":

{
  "request": {
    "method": "PUT",
      "urlPath": "/minions",
      "bodyPatterns": [ {
      "matchesJsonPath": "$['blah']"
    }]
  },
  "response": {
  "status": 400
  }
}

To check for the presence of any key in any json, we now just need to know two more things.  The first is that to parse out or match a nested key, we would do something like this:

$['family']['children']

Or, to use the more hip syntax:

$.name.children

This would return 16 given the following json:

{
    "family": {
        "children": 16
    }
}

The second thing we need is knowing how to handle arrays.  For example, what if the children were actually listed and we wanted to find out Dave's mental state:

{
    "family": {
        "children": [ 
            { "Dave": "thinking" },
            { "Stuart": "silly" }
        ]
    }
}

Note that now we are using "Dave" as a key rather than a value.  If we wanted to get Dave's mental state (which is "thinking"), we could use the following JsonPath:

$.family.children[0].Dave

If `$.family.children` returns an array, then `[0]` is the first element of that array.  (Subscript 1 instead of 0, on the other hand, would not match anything because there is only one item in the array.)  And if we didn't really care about what position "Dave" occupies in the array as long as he is in the array, then we could use a wildcard:

$.family.children[*].Dave

Given the json above, this JsonPath should return "thinking".  You can try it using the JSONPath Online Evaluator.  But keep in mind that that evaluator will not work for filters used in the next section (while WireMock will work just fine).

 

BY KEY AND VALUE

Next, let's say that, instead of checking for the presence of a specific key (e.g. "name" for our happy-path scenario and "blah" for our rainy-day scenario), we want to go a step further and check for the presence of a specific value of a specific key.  For example, let's say we want to return 200 if, and only if, key "name" is equal to "Dave".  For everything else, we are okay with a 404.  Here is how we would do that.  Remove your other mapping files and instead create one called "dave.json":

{
  "request": {
    "method": "PUT",
    "urlPath": "/minions",
    "bodyPatterns": [ {
      "matchesJsonPath": "$.[?($.name == 'Dave')]"
    }]
  },
  "response": {
    "status": 200
  }
}

Or, similarly, you could use the less cool but perhaps still more understandable JsonPath syntax like so:

$[?($['name'] == 'Dave')]

Here things got a little confusing for me. I expected the syntax to be $.?() instead of $.[?()]. But for some reason filters require array syntax.

Sending {"name":"Dave"} to the server will get you a 200. That's because, given that request, $.name returns "Dave", and "Dave" indeed equals "Dave". Well, we used single quotes because we were embedding them inside double quotes, but same thing: 'Dave' == 'Dave' returns true.

Sending {"name":"Stuart"}, on the other hand, will get you a 404—not a 400, but a 404. Because the mock server does not have any responses mapped to the request you are sending it. The ?() is a filter expression. Akin to a query string or a filter you might see in an URL (here, here or here, for example), it starts with a question mark as if you are trying to ask a question.

It is also not uncommon to see the @ operator in JsonPath filter expressions. Instead of {"name":"Dave"} as our http request, consider the following nested json:

{
    "name": {
        "first": "Dave",
        "last": "Minion!"
    }
}

The JsonPath expression that would match $.name.first == "Dave" is:

$.[?($.name.first == 'Dave')]

Perhaps the more common solution, however, would make use of the @ operator:

$.name[?(@.first == 'Dave')]

 

For more

For WireMock request matching, see the docs.  For another interesting feature of WireMock, see https://github.com/wbraynen/wiremock

For more on JsonPath syntax and implementations, see Stefan Goessner original JsonPath project (from 2007), as well as a Java port of that implementation (which is relevant because WireMock is also written in Java and so likely uses that Java port and also because the Java port has good documentation and examples).  For the insatiably curious, there is also a JavaScript (node.js) port.  I am not aware of any Swift ports, although this certainly made me look at SwiftyJson in a new light.