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.57k stars 1.07k forks source link

Serialized proxy expectations include overly specific URL matching #81

Closed benson-basis closed 9 years ago

benson-basis commented 9 years ago

When I deserialize the results of using the proxy to capture traffic, I end up with a completely specific URL, including the port. Could there be a with() that took an HTTP request and fuzzed it by allowing any port in the URL?

jamesdbloom commented 9 years ago

Can you give me a concrete example so I can fully understand the exact scenario.

benson-basis commented 9 years ago

I added a proxy to some existing unit tests of a service, and serialized the expectations. Each resulting expectation looks like the following. So, if I then deserialize and feed the results in as a request to with(), the URL is expected to match, down to the port number. So I had to write some code to reconstruct a new request with everything except the URL. Maybe that's perfectly OK, but I ended up wishing for something a bit less verbose than all those with calls to pieces of the request builder.

[ {
  "httpRequest" : {
    "method" : "GET",
    "url" : "http://localhost:60766/raas/ping",
    "path" : "/raas/ping",
    "headers" : [ {
      "name" : "Content-Type",
      "values" : [ "*/*" ]
    }, {
      "name" : "Accept",
      "values" : [ "application/json" ]
    }, {
      "name" : "User-Agent",
      "values" : [ "Apache CXF 2.7.11" ]
    }, {
      "name" : "Cache-Control",
      "values" : [ "no-cache" ]
    }, {
      "name" : "Pragma",
      "values" : [ "no-cache" ]
    }, {
      "name" : "Host",
      "values" : [ "localhost:60766" ]
    } ]
  },
  "httpResponse" : {
    "statusCode" : 200,
    "body" : "{\"message\":\"RaaS at your service\",\"time\":1413808432181}",
    "headers" : [ {
      "name" : "Date",
      "values" : [ "Mon, 20 Oct 2014 12:33:52 GMT" ]
    }, {
      "name" : "Content-Type",
      "values" : [ "application/json" ]
    }, {
      "name" : "Server",
      "values" : [ "Jetty(8.1.14.v20131031)" ]
    } ]
  },
  "times" : {
    "remainingTimes" : 1,
    "unlimited" : false
  }
} ]
jamesdbloom commented 9 years ago

When doing a match you only need to specify the fields which you want to match on. For example you could quite easily match the request above as follows:

{
  "httpRequest" : {
    "method" : "GET",
    "path" : "/raas/ping"
  },
  "httpResponse" : {
    "statusCode" : 200,
    "body" : "{\"message\":\"RaaS at your service\",\"time\":1413808432181}",
    "headers" : [ {
      "name" : "Date",
      "values" : [ "Mon, 20 Oct 2014 12:33:52 GMT" ]
    }, {
      "name" : "Content-Type",
      "values" : [ "application/json" ]
    }, {
      "name" : "Server",
      "values" : [ "Jetty(8.1.14.v20131031)" ]
    } ]
  }
}

This would then tell the MockServer any GET request with the path "/raas/ping" return the specified response. If that is not flexible enough you can also use regex in any of the string fields such as url, path or header values. This way you can make the expectation as flexible as you like.

The proxy just records what it see and dumps that out. The intention of building the proxy is that sometimes your having to mock and existing system where its not clear what all the interactions are and it is helpful to use the proxy to understand how the system behaves. So its just a guide and not specifically intended to be copy and pasted exactly into test expectations.

Does that answer help? Or did I misunderstand you problem?

benson-basis commented 9 years ago

I get it. I had written:

 Expectation[] expectations = expectationSerializer.deserializeArray(jsonString);
        for (Expectation expectation : expectations) {
            HttpRequest request = expectation.getHttpRequest();
            boolean get = "get".equalsIgnoreCase(request.getMethod());
            /*
             * The request side defines what is a match. We don't want a match on the URL;
             * that will have an ephemeral port number in it. The body is an interesting
             * question, leaving it here requires the client test to have just-the-same
             * body, which may be unreasonable due to json serialization quirks.
             * So we leave it out, and assume that we don't want to have multiple
             * request-response pairs in the mock that differ only in request body.
             */
            mockServerClient.when(HttpRequest.request()
                    .withPath(request.getPath())
                    .withHeaders(filterHeaders(request.getHeaders(), get))
                    .withMethod(request.getMethod())
                    .withCookies(request.getCookies())
                    .withQueryStringParameters(request.getQueryStringParameters()))
                    .respond(expectation.getHttpResponse(false));
        }

and I was imagining a more concise way to say: use this serialized request, but only this much ... and, by the way, use case-insensitive matching on header names and appropriate header values.

jamesdbloom commented 9 years ago

MockServer doesn't support record and replay of expectations because from experience that leads to very brittle and expensive to support tests. If requests are recorded and replayed then they are very hard to update and over detailed. In addition often the same recorded values are shared between different tests which adds a lot of complex due to the single responsibility and encapsulation principles being broken.

However MockServer does do case in-sensitive matching of header names.

I'm going to close this issue but please re-open it if you think there is a bug or a feature request,

benson-basis commented 9 years ago

Fair enough.