mock-server / mockserver

MockServer enables easy mocking of any system you integrate with via HTTP or HTTPS with clients written in Java, JavaScript and Ruby. MockServer also includes a proxy that introspects all proxied traffic including encrypted SSL traffic and supports Port Forwarding, Web Proxying (i.e. HTTP proxy), HTTPS Tunneling Proxying (using HTTP CONNECT) and SOCKS Proxying (i.e. dynamic port forwarding).
http://mock-server.com
Apache License 2.0
4.54k stars 1.06k forks source link

Cannot match null values in json body when using strict mode #1023

Closed cchangenot closed 2 years ago

cchangenot commented 3 years ago

Describe the issue When using strict mode and json body, no match found for a request with null values in some json fields.

What you are trying to do I want to be sure that a request has been sent with a json body that contains null values for some specific fields. If i don't use the strict mode, the json fields with null values are ignored and i can't be sure that that the field was null for real.

MockServer version 5.11.1

To Reproduce Here is a small failing test case :

@Test
public void myTest() throws IOException {
    // Given
    MockServerClient mockServerClient = new MockServerClient("localhost", 1080).reset();

    RequestDefinition requestDefinition = request().withMethod("POST")
                                                   .withPath("/some/path")
                                                   .withBody(json(singletonMap("myNullProperty", null), MatchType.STRICT));

    mockServerClient.when(requestDefinition).respond(response().withStatusCode(204));

    RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{\"myNullProperty\":null}");
    Request request = new Request.Builder().post(body)
                                           .url("http://localhost:1080/some/path")
                                           .build();

    //When
    new OkHttpClient().newCall(request).execute();

    // Then
    mockServerClient.verify(requestDefinition);
}
  1. How you are running MockServer (i.e maven plugin, docker, etc) Using docker
  2. Code you used to create expectations
    
    MockServerClient mockServerClient = new MockServerClient("localhost", 1080).reset();

RequestDefinition requestDefinition = request().withMethod("POST") .withPath("/some/path") .withBody(json(singletonMap("myNullProperty", null), MatchType.STRICT));

mockServerClient.when(requestDefinition).respond(response().withStatusCode(204));

3. What error you saw

java.lang.AssertionError: Request sequence not found, 

Expected :
```json
[ {
  "method" : "POST",
  "path" : "/some/path",
  "body" : {
    "type" : "JSON",
    "json" : { },
    "rawBytes" : "e30=",
    "matchType" : "STRICT"
  }
} ]

Actual :

{
  "method" : "POST",
  "path" : "/some/path",
  "body" : {
    "myNullProperty" : null
  },
  "headers" : {
    "Connection" : [ "Keep-Alive" ],
    "User-Agent" : [ "okhttp/3.14.9" ],
    "Host" : [ "localhost:1080" ],
    "Accept-Encoding" : [ "gzip" ],
    "Content-Length" : [ "23" ],
    "Content-Type" : [ "application/json; charset=utf-8" ]
  },
  "keepAlive" : true,
  "secure" : false
}

Expected behaviour The strict expectations handle null values.

I think that the problem is that null values are removed from the expectation when using strict mode. I suspect these are the problematic lines (ObjectMapperFactory#buildObjectMapper):

// remove empty values from JSON
swallowThrowable(() -> objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT));
swallowThrowable(() -> objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL));
swallowThrowable(() -> objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY));

MockServer Log

mockserver_1       | 2021-05-10 14:38:40 5.11.1 INFO logger level is INFO, change using:
mockserver_1       |  - 'ConfigurationProperties.logLevel(String level)' in Java code,
mockserver_1       |  - '-logLevel' command line argument,
mockserver_1       |  - 'mockserver.logLevel' JVM system property or,
mockserver_1       |  - 'mockserver.logLevel' property value in 'mockserver.properties' 
mockserver_1       | 2021-05-10 14:38:40 5.11.1 INFO 1080 started on port: 1080 
mockserver_1       | 2021-05-10 14:38:57 5.11.1 INFO 1080 resetting all expectations and request logs 
mockserver_1       | 2021-05-10 14:38:58 5.11.1 INFO 1080 creating expectation:
mockserver_1       |   {
mockserver_1       |     "id" : "60952d6a-ed65-468d-b3dd-60e653d147c6",
mockserver_1       |     "priority" : 0,
mockserver_1       |     "httpRequest" : {
mockserver_1       |       "method" : "POST",
mockserver_1       |       "path" : "/some/path",
mockserver_1       |       "body" : {
mockserver_1       |         "type" : "JSON",
mockserver_1       |         "json" : { },
mockserver_1       |         "matchType" : "STRICT"
mockserver_1       |       }
mockserver_1       |     },
mockserver_1       |     "times" : {
mockserver_1       |       "unlimited" : true
mockserver_1       |     },
mockserver_1       |     "timeToLive" : {
mockserver_1       |       "unlimited" : true
mockserver_1       |     },
mockserver_1       |     "httpResponse" : {
mockserver_1       |       "statusCode" : 204
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  with id:
mockserver_1       |   60952d6a-ed65-468d-b3dd-60e653d147c6
mockserver_1       |  
mockserver_1       | 2021-05-10 14:38:59 5.11.1 INFO 1080 received request:
mockserver_1       |   {
mockserver_1       |     "method" : "POST",
mockserver_1       |     "path" : "/some/path",
mockserver_1       |     "headers" : {
mockserver_1       |       "Content-Type" : [ "application/json; charset=utf-8" ],
mockserver_1       |       "Content-Length" : [ "23" ],
mockserver_1       |       "Host" : [ "localhost:1080" ],
mockserver_1       |       "Connection" : [ "Keep-Alive" ],
mockserver_1       |       "Accept-Encoding" : [ "gzip" ],
mockserver_1       |       "User-Agent" : [ "okhttp/3.14.9" ]
mockserver_1       |     },
mockserver_1       |     "keepAlive" : true,
mockserver_1       |     "secure" : false,
mockserver_1       |     "body" : {
mockserver_1       |       "myNullProperty" : null
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  
mockserver_1       | 2021-05-10 14:38:59 5.11.1 INFO 1080 request:
mockserver_1       |   {
mockserver_1       |     "method" : "POST",
mockserver_1       |     "path" : "/some/path",
mockserver_1       |     "headers" : {
mockserver_1       |       "Content-Type" : [ "application/json; charset=utf-8" ],
mockserver_1       |       "Content-Length" : [ "23" ],
mockserver_1       |       "Host" : [ "localhost:1080" ],
mockserver_1       |       "Connection" : [ "Keep-Alive" ],
mockserver_1       |       "Accept-Encoding" : [ "gzip" ],
mockserver_1       |       "User-Agent" : [ "okhttp/3.14.9" ]
mockserver_1       |     },
mockserver_1       |     "keepAlive" : true,
mockserver_1       |     "secure" : false,
mockserver_1       |     "body" : {
mockserver_1       |       "myNullProperty" : null
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  didn't match expectation:
mockserver_1       |   {
mockserver_1       |     "id" : "60952d6a-ed65-468d-b3dd-60e653d147c6",
mockserver_1       |     "priority" : 0,
mockserver_1       |     "httpRequest" : {
mockserver_1       |       "method" : "POST",
mockserver_1       |       "path" : "/some/path",
mockserver_1       |       "body" : {
mockserver_1       |         "type" : "JSON",
mockserver_1       |         "json" : { },
mockserver_1       |         "matchType" : "STRICT"
mockserver_1       |       }
mockserver_1       |     },
mockserver_1       |     "times" : {
mockserver_1       |       "unlimited" : true
mockserver_1       |     },
mockserver_1       |     "timeToLive" : {
mockserver_1       |       "unlimited" : true
mockserver_1       |     },
mockserver_1       |     "httpResponse" : {
mockserver_1       |       "statusCode" : 204
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  because:
mockserver_1       |   method matched
mockserver_1       |   path matched
mockserver_1       |   body didn't match
mockserver_1       |  
mockserver_1       | 2021-05-10 14:38:59 5.11.1 INFO 1080 no expectation for:
mockserver_1       |   {
mockserver_1       |     "method" : "POST",
mockserver_1       |     "path" : "/some/path",
mockserver_1       |     "headers" : {
mockserver_1       |       "Content-Type" : [ "application/json; charset=utf-8" ],
mockserver_1       |       "Content-Length" : [ "23" ],
mockserver_1       |       "Host" : [ "localhost:1080" ],
mockserver_1       |       "Connection" : [ "Keep-Alive" ],
mockserver_1       |       "Accept-Encoding" : [ "gzip" ],
mockserver_1       |       "User-Agent" : [ "okhttp/3.14.9" ]
mockserver_1       |     },
mockserver_1       |     "keepAlive" : true,
mockserver_1       |     "secure" : false,
mockserver_1       |     "body" : {
mockserver_1       |       "myNullProperty" : null
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  returning response:
mockserver_1       |   {
mockserver_1       |     "statusCode" : 404,
mockserver_1       |     "reasonPhrase" : "Not Found"
mockserver_1       |   }
mockserver_1       |  
mockserver_1       | 2021-05-10 14:38:59 5.11.1 INFO verifying sequence that match:
mockserver_1       |   {
mockserver_1       |     "httpRequests" : [ {
mockserver_1       |       "method" : "POST",
mockserver_1       |       "path" : "/some/path",
mockserver_1       |       "body" : {
mockserver_1       |         "type" : "JSON",
mockserver_1       |         "json" : { },
mockserver_1       |         "matchType" : "STRICT"
mockserver_1       |       }
mockserver_1       |     } ]
mockserver_1       |   }
mockserver_1       |  
mockserver_1       | 2021-05-10 14:38:59 5.11.1 INFO request sequence not found, expected:
mockserver_1       |   [{
mockserver_1       |     "method" : "POST",
mockserver_1       |     "path" : "/some/path",
mockserver_1       |     "body" : {
mockserver_1       |       "type" : "JSON",
mockserver_1       |       "json" : { },
mockserver_1       |       "matchType" : "STRICT"
mockserver_1       |     }
mockserver_1       |   }]
mockserver_1       |  but was:
mockserver_1       |   {
mockserver_1       |     "method" : "POST",
mockserver_1       |     "path" : "/some/path",
mockserver_1       |     "headers" : {
mockserver_1       |       "Content-Type" : [ "application/json; charset=utf-8" ],
mockserver_1       |       "Content-Length" : [ "23" ],
mockserver_1       |       "Host" : [ "localhost:1080" ],
mockserver_1       |       "Connection" : [ "Keep-Alive" ],
mockserver_1       |       "Accept-Encoding" : [ "gzip" ],
mockserver_1       |       "User-Agent" : [ "okhttp/3.14.9" ]
mockserver_1       |     },
mockserver_1       |     "keepAlive" : true,
mockserver_1       |     "secure" : false,
mockserver_1       |     "body" : {
mockserver_1       |       "myNullProperty" : null
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  
jamesdbloom commented 2 years ago

This is because the expectation you have created has an empty json object as the body because the JSON serialisation of Map skips null fields. If you want to match a null value in json you need to do the following:

RequestDefinition requestDefinition = request()
    .withMethod("POST")
    .withPath("/some/path")
    .withBody(json("{\n" +
        "      \"myNullProperty\" : null\n" +
        "    }", MatchType.STRICT));

The following complete example works:

// Given
MockServerClient mockServerClient = ClientAndServer.startClientAndServer(1080);

RequestDefinition requestDefinition = request()
    .withMethod("POST")
    .withPath("/some/path")
    .withBody(json("{\n" +
        "      \"myNullProperty\" : null\n" +
        "    }", MatchType.STRICT));

mockServerClient.when(requestDefinition).respond(response().withStatusCode(204));

RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{\"myNullProperty\":null}");
Request request = new Request.Builder().post(body)
    .url("http://localhost:1080/some/path")
    .build();

//When
new OkHttpClient().newCall(request).execute();

// Then
mockServerClient.verify(requestDefinition);

Where as your example doesn't, as follows:

// Given
MockServerClient mockServerClient = ClientAndServer.startClientAndServer(1080);

Map<String, String> map = new HashMap<>();
map.put("myNullProperty", null);

RequestDefinition requestDefinition = request()
    .withMethod("POST")
    .withPath("/some/path")
    .withBody(json(map, MatchType.STRICT));

mockServerClient.when(requestDefinition).respond(response().withStatusCode(204));

RequestBody body = RequestBody.create(MediaType.parse("application/json"), "{\"myNullProperty\":null}");
Request request = new Request.Builder().post(body)
    .url("http://localhost:1080/some/path")
    .build();

//When
new OkHttpClient().newCall(request).execute();

// Then
mockServerClient.verify(requestDefinition);

Because as show in the log you provided the expectation has an empty body:

mockserver_1       | 2021-05-10 14:38:58 5.11.1 INFO 1080 creating expectation:
mockserver_1       |   {
mockserver_1       |     "id" : "60952d6a-ed65-468d-b3dd-60e653d147c6",
mockserver_1       |     "priority" : 0,
mockserver_1       |     "httpRequest" : {
mockserver_1       |       "method" : "POST",
mockserver_1       |       "path" : "/some/path",
mockserver_1       |       "body" : {
mockserver_1       |         "type" : "JSON",
mockserver_1       |         "json" : { },
mockserver_1       |         "matchType" : "STRICT"
mockserver_1       |       }
mockserver_1       |     },
mockserver_1       |     "times" : {
mockserver_1       |       "unlimited" : true
mockserver_1       |     },
mockserver_1       |     "timeToLive" : {
mockserver_1       |       "unlimited" : true
mockserver_1       |     },
mockserver_1       |     "httpResponse" : {
mockserver_1       |       "statusCode" : 204
mockserver_1       |     }
mockserver_1       |   }
mockserver_1       |  with id:
mockserver_1       |   60952d6a-ed65-468d-b3dd-60e653d147c6