Wiremock: how to match a JSON request that does NOT have a specific property?

Viewed 4593

I'm trying to mock an API call that accepts a JSON body in a POST and it has two possible responses:

  1. if body contains SearchCenter property, answer with response A
  2. if body does NOT contain SearchCenter, answer with response B

In the Request Matching chapter of Wiremock documentation it only shows how to positively match JSON, it does not show how to match missing properties.

Sample request with SearchCenter:

{
    "GeoCoordinatesResponseFormat": "DecimalDegree",
    "ProviderID": "bla bla",
    "SearchCenter": {
        "GeoCoordinates": {
            "DecimalDegree": {
                "Latitude": "{{search_lat}}",
                "Longitude": "{{search_lon}}"
            }
        },
        "Radius": {{search_radius}}
    }
}

Sample request without SearchCenter:

{
    "GeoCoordinatesResponseFormat": "DecimalDegree",
    "ProviderID": "bla bla"
}
2 Answers

In order to match a missing JSON property, you can use the matchingJsonPath operator in combination with the absent(), like this:

.withRequestBody(matchingJsonPath("$.SearchCenter", absent()))

Edit: I may have gotten ahead of myself -- I think there is an easier solution for matching. You could use negative matching with regex. (Negative Lookahead) I still think I prefer the priorities method.

stubFor(any(urlPathEqualTo("/some-endpoint"))
    .withRequestBody(matchingJsonPath("$.?!SearchCenter"))
    .willReturn(ok("Body does not contain Search Center"));

stubFor(any(urlPathEqualTo("/some-endpoint"))
    .withRequestBody(matchingJsonPath("$.SearchCenter"))
    .willReturn(ok("Body does contain Search Center"));

There are two ways you can accomplish this. The first would use a regex matcher/not matcher. Assuming you're using stubs...

stubFor(any(urlPathEqualTo("/some-endpoint"))
    .withRequestBody(notMatching("\"SearchCenter\": \{ .* \}"))
    .willReturn(ok("Body does not contain Search Center"));

stubFor(any(urlPathEqualTo("/some-endpoint"))
    .withRequestBody(matchingJsonPath("$.SearchCenter"))
    .willReturn(ok("Body does contain Search Center"));

This does a regex match on not matching for checking if the field does not exist, and then JSON path matching for the case where the field does exist. (I didn't test out the actual code, so the regex matching / JSON path might need some tweaking.)

However, I think a better solution would be to have a more specific match return one response, and the less specific match return the other response. We can achieve this through priorities.

stubFor(any(urlPathEqualTo("/some-endpoint"))
    .atPriority(1)
    .withRequestBody(matchingJsonPath("$.SearchCenter"))
    .willReturn(ok("Body does contain Search Center"));

stubFor(any(urlPathEqualTo("/some-endpoint"))
    .atPriority(2)
    .willReturn(ok("Body does not contain Search Center"));

Information on Priorities can be found here. The tl;dr on Priorities is that they force WireMock to check for certain matches before others. In this case, we're going to check if the request body contains the field we want ("Search Center") and if it does, we return a match. If we had other stubs at the same priority (1), those would also get checked before moving onto the next priority. In the case where the field we want to match on is not found, we move to the next priority. Because the stub at Priority 2 does not need to match the JSON Path in the Request Body, we will match.

My preference for priorities over specific matching twice is simply because I like the "fallback" behavior that having a generic stub provides, where any calls to "/some-endpoint" will be fulfilled with the generic response.

Related