equalToJson: compare structure only, ignore values

508 views
Skip to first unread message

João Eiras

unread,
Nov 2, 2018, 8:03:48 AM11/2/18
to wiremock-user
Howdy,

In my project I depend on a REST service with 20 plus end-points which requires request payloads with a verbose and complex structure. I do have a JSON like specification of the REST service API which I use to generate controllers, tests and wiremock template files.

What I'd like to do if to have a equalToJson() like predicate that ignores the values but still compares structure and types, like checking that my request body matches a template.

For instance, if have the following rule

"bodyPatterns": [{
"equalToJsonTemplate": {
"aStringField": "",
"aIntField": 1,
"aNullField": null,
"anObject": {
"anotherField": 1
},
"aList": [1, "2", 3 ],
},
    "ignoreExtraElements": true}]

the following would match

{
"aStringField": "something",
"aIntField": 123,
"aNullField": {},
"anObject": {
"anotherField": 123
},
"aList": [123, "456", 789],
}

null would be like a wildcard, anything matches but the field needs to be present. Else leaving the field out and using ignoreExtraElement would also act like a wildcard and make the field optional.

Is there a similar feature ? Or can I suggest the feature ?

There is plenty of room for suggestions like allowing fields to specify constraints on the values, but then that would start to look like the use of matchesJsonPath and using sub expressions to match the whole tree.

Cheers.

Tom Akehurst

unread,
Nov 2, 2018, 9:48:59 AM11/2/18
to wiremock-user
What do you think of the idea of using JSON Schema instead of an example?

I've been mulling this over for a little while, and I think the main advantage would be that it provides a much more flexible way to express a valid document.

João Eiras

unread,
Nov 2, 2018, 1:34:05 PM11/2/18
to wiremock-user


On Friday, November 2, 2018 at 2:48:59 PM UTC+1, Tom Akehurst wrote:
What do you think of the idea of using JSON Schema instead of an example?


Oh, that would be very interesting ! And much more flexible.

But, perhaps a bit overkill for some simpler use cases. Like, in my case I have an example reply and would just paste that in the wiremock mapping file and wiremock would compare the structure.


Tom Akehurst

unread,
Nov 5, 2018, 12:34:43 PM11/5/18
to wiremock-user
Maybe both of these cases would be better implemented as extension libraries anyway.

Sometime soon I'm going to make a change to the custom matcher logic so that custom matchers can be combined with standard ones. Once that's done this should be pretty easy to do.

chandral...@gmail.com

unread,
Jan 15, 2019, 9:37:39 AM1/15/19
to wiremock-user
I am also dealing with a problem similar to Joao. So I have a lots of http requests from my application which has arbitrary values in it like timestamp and UUID. Do you think your change would address mine too?

Tom Akehurst

unread,
Jan 15, 2019, 12:48:32 PM1/15/19
to wiremock-user
The change to support custom + standard matchers is released in WireMock 2.20.0.

You'd still need to write a custom matcher to do structural comparison.

chandral...@gmail.com

unread,
Jan 15, 2019, 4:51:08 PM1/15/19
to wiremock-user
Do you have an example to match URI and request body with custom matcher?

Tom Akehurst

unread,
Jan 15, 2019, 4:53:47 PM1/15/19
to wiremock-user

chandral...@gmail.com

unread,
Jan 16, 2019, 9:52:16 AM1/16/19
to wiremock-user
So, I went through all the Acceptance tests. I couldn't find a test where a url and a request is being matched. I am work on a post request where I need to match both url and request.

Tom Akehurst

unread,
Jan 16, 2019, 10:54:14 AM1/16/19
to wiremock-user
There isn't a test case that exactly matches what you're trying to do, but the one I linked to is pretty close - it matches the URL using the standard URL matcher, and also uses a custom matcher, which can contain any logic you wish. You could write a matcher that extracts the request body and validates e.g. against a schema, or just tests for specific structural elements.

chandral...@gmail.com

unread,
Jan 17, 2019, 10:49:59 AM1/17/19
to wiremock-user

Thanks Tom! I was able to write a custom matcher and get through my integration test. But I have another problem now. :) 

So I have the same endpoint getting called twice in my test. For the first call wrote a basic matching and for the second one I wrote a custom request matcher and referring to it by name(added as an extension in wiremock rule).

And when I ran the test, the first call is going the custom matcher instead of a basic match.

Basic match
==========

stubFor(WireMock.post(urlEqualTo(url)).withRequestBody(equalToJson(readFile(requestFile)))

                    .willReturn(aResponse().withStatus(HttpStatus.SC_OK)

                            .withBody(replaceValues(readFile(responseFile), responseParams))));


Custom match

==============

stubFor(post(urlPathMatching(url)).withRequestBody(equalToJson(readFile(requestFile)))

                .andMatching("myMatcher", Parameters.one("stub-path", requestFile))

                .willReturn(aResponse().withStatus(HttpStatus.SC_OK).withBody(replaceValues(readFile(responseFile), responseParams))));


url is same for both the stubs above.


WiremockRule

============

@Rule

public WireMockRule wireMockRule = new WireMockRule( wireMockConfig().port(WIREMOCK_PORT).extensions(DynamoMatcher.class).notifier(new ConsoleNotifier(false)));



Am I doing anything wrong?

Tom Akehurst

unread,
Jan 17, 2019, 10:53:46 AM1/17/19
to wiremock-user
Hard to know without seeing the requests you're making and the implementation of the custom matchers, but I'd guess that the custom matcher is matching overly loosely, and is therefore matching both your requests rather than just the one you're intending.

chandral...@gmail.com

unread,
Jan 17, 2019, 11:04:34 AM1/17/19
to wiremock-user
Below is my matcher:

@Override

    public String getName() {

        return "myMatcher";

    }

   

    @Override

    public MatchResult match(Request request, Parameters parameters) {


        String stubbedRequestFilePath = parameters.get("stub-path").toString();

        try {

            String stubbedRequest = Stubber.readFile(stubbedRequestFilePath);

            String requestBody = new String(request.getBody());

            assertJsonEquals(stubbedRequest, requestBody);

        } catch (IOException e) {

            e.printStackTrace();

        } catch (URISyntaxException e) {

            e.printStackTrace();

        }


        return null;

    }


so when I debug, the stubbedRequest is same as what I am stubbing for a custom matcher. But the requestBody contains the request of first basic stub. I am assuming that, since the url is same for both the stubs it is blindly getting into custom matcher. or custom stub overriding the basic stub. 


Is it possible to specify the wiremock rule for a single stub?

Message has been deleted

Tom Akehurst

unread,
Jan 17, 2019, 12:00:03 PM1/17/19
to wiremock-user
I'm not sure what you're trying to achieve here.

Why are you matching the request body both using equalToJson() and in the custom matcher?

chandral...@gmail.com

unread,
Jan 17, 2019, 12:20:54 PM1/17/19
to wiremock-user
Sorry to confuse you. Apologies. I gave the wrong stub for custom matcher. Below is the right one.

Stub
=====

String requestStub = replaceValues(readFile(requestFile), requestParams);

stubFor(post(urlPathMatching(url)).withRequestBody(equalToJson(requestStub))

                .andMatching("dynamoMatcher", Parameters.one("request-stub", requestStub))

                .willReturn(aResponse().withStatus(HttpStatus.SC_OK).withBody(readFile(responseFile))));


Matcher

========

@Override

public MatchResult match(Request request, Parameters parameters) {


        String stubbedRequest = parameters.get("request-stub").toString();

            String requestBody = new String(request.getBody());

            assertJsonEquals(stubbedRequest, requestBody);


        return null;

}


I am replacing the value of session_id field in my requestStub and using matcher to match the actual request(requestBody) and with subbedRequest by ignoring some timestamp fields in it. These timestamp field values change for every request.

So in stub just a field value is changed. In Matcher the request is matched by ignoring timestamp fields. 

chandral...@gmail.com

unread,
Jan 18, 2019, 11:51:23 AM1/18/19
to wiremock-user
Hey Tom,

I am not still not able to resolve this issue. Do you have an example where a single test uses two stubs(one with basic matching and second with custom matching) with same endpoint ?

Yesterday I tried registering the custom matching with wiremock but then I was not able to resolve.

    @Rule

    public WireMockRule wireMockRule = new WireMockRule( wireMockConfig().port(WIREMOCK_PORT).extensions(MyMaytcher.class).notifier(new ConsoleNotifier(false)));


    @Before

    public void setup() {

        wireMock = WireMock.create().port(wireMockRule.port()).build();

    }


    String firstRequestStub = replaceValues(readFile(requestFile), requestParams);

    stubFor(WireMock.post(urlEqualTo(url)).withRequestBody(equalToJson(firstRequestStub))

                .willReturn(aResponse().withStatus(HttpStatus.SC_OK).withBody(readFile(firstResponseFile))));



    String secondRequestStub = replaceValues(readFile(requestFile), requestParams);

    stubFor(post(urlPathMatching(url)).withRequestBody(equalToJson(secondRequestStub))

                .andMatching("dynamoMatcher", Parameters.one("request-stub", secondRequestStub))

                .willReturn(aResponse().withStatus(HttpStatus.SC_OK).withBody(readFile(secondResponseFile))));


I am using the above two stubs for two external calls in a single test which have same endpoint and different requests and responses. For some reason, for the first external call, it steps into the custom matcher of second stub and it fails.. 


Please assist me on this. 

Reply all
Reply to author
Forward
0 new messages