SpectoLabs / hoverfly-java

Java binding for Hoverfly
Apache License 2.0
168 stars 58 forks source link

StubServiceBuilder needs an unprocessed(UnProcessedHandler handler) method. #47

Open paul-hammant opened 7 years ago

paul-hammant commented 7 years ago

I might be mocking my service endpoints, but wanting my HTML, JS, CSS, PNG to be statically served.

service(domain).unprocessed(myUnProcessedHandler)

UnProcessedHandler would have a method:

Response handleUnprocessed(Request req) {
}

You should be able to fix response codes, mimetypes, set bodies in there. Also, a Response.DO_ACTUAL_PROXY_HTTP_CALL would be nice (for my HTML etc need).

tommysitu commented 7 years ago

@paul-hammant, did you mean the StubServiceBuilder should simulate response with static files? Could you elaborate a bit more on the use case please?

ph-ms commented 7 years ago

I might unprocessed() to be able to serve static content, yes.

Others might want a callback API so they can participate programmatically in situations where an endpoint wasn't mocked. I.e. do deterministic code in that situation, rather than declared-upfront request/response pairings.

mogronalol commented 7 years ago

@paul-hammant I think there are two problems here.

The first one I would do something more like:

service("www.my-test.com")
   .post("/api/bookings").body("{\"flightId\": \"1\"}")
   .willReturn(created("http://localhost/api/bookings/1"))
   .unprocessed()
   .willReturn(...)

It would still be using a matcher internally, it's just that it matches on the host and ignores all the other fields. It would take lower level of precedence over stronger matches (By this I mean, if there is a match on more than one field, that's the response it should chose over a match on a single field).

For the second one, depending on what you are trying to do, there could be a few solutions.

  1. You can configure the JVM to proxy to exclude www.static.com and include www.service.com (this assumes your service is on different domain to static content). This would be an easy way of not proxying a service under test, but proxying it's external dependency.
  2. Rather than using the JVM proxy, we can introduce the functionality via Hoverfly using destination filters
  3. We could introduce the http equivalent of Mockito Spy or partial mock. The idea would be that if there is a match it would return it, otherwise call out to the real service (This would work across domains)

Any of these settings would be global.

Judging from what you've said, I think number 3 would be most suitable. I'm curious if that would be a sufficient alternative to your per matcher Response.DO_ACTUAL_PROXY_HTTP_CALL.

ph-ms commented 7 years ago

For the first one will the args passed in to .willReturn(...) have an ability to determine the URL/verb sought?

I was imagining that the unprocessed(..) would not have a corresponding willReturn(..)

A Mockito-style spy would be great.

mogronalol commented 7 years ago

Hm, I see what you mean. So when it doesn't match on anything other than the host, you'd want some sort of logic to used to generate a response. Could that work as different matchers though? Something like:

service("www.my-test.com")
   .post("/api/bookings").body("{\"flightId\": \"1\"}")
   .willReturn(created("http://localhost/api/bookings/1"))
   .unprocessedGet() // Matches on any GET to www.my-test.com with no stronger match
   .willReturn(notFound())
   .unprocessedPost() // Matches on any POST to www.my-test.com with no stronger match
   .willReturn(internalServerError())
   .unprocessed() // Matches on any METHOD to www.my-test.com with no stronger match
   .willReturn(internalServerError())
tommysitu commented 7 years ago

I think the following might be more clear:

service("www.my-test.com")
   .post("/api/bookings").body("{\"flightId\": \"1\"}")
   .willReturn(created("http://localhost/api/bookings/1"))

   .unMatched(request().get("/html/*")) // Matches on any GET to www.my-test.com/html/* with no stronger match
   .willReturn(notFound())

   .unMatched(request().post()) // Matches on any POST to www.my-test.com with no stronger match
   .willReturn(internalServerError())

   .unMatched() // Matches on any METHOD to www.my-test.com with no stronger match
   .willReturn(internalServerError())

Enable destination filtering to allow call to real service is already captured in issue #19

Have a SPY mode is a good idea.

ph-ms commented 7 years ago

The dynamic case needs to be made

.unMatched(request().get("*.html")).willReturn(letMeGetThatForYou())

Like so: .unMatched(myCapturingMatcher()).willReturn(letMeGetThatForYouUsingSomethingCapturedOnShockHorrorThreadLocal())

tommysitu commented 7 years ago

The DSL is just another way of creating a static simulation file to preload into Hoverfly. It is not compiled dynamically to generate a response.

You are probably looking for the [Synthesize Mode] (http://hoverfly.io/en/latest/pages/keyconcepts/modes/synthesize.html)

It would require the support of Middleware #7.

mogronalol commented 7 years ago

@paul-hammant Spy behaviour will be put in Hoverfly first, here is an issue.

mogronalol commented 7 years ago

@ph-ms In reference to unmatched, you can't actually add any logic like this into Hoverfly Java, it would have to be done using middleware, which is yet to be supported. I can think of another workaround to get it to work, but it feels quite hacky.

Could you give me an example of your specific use case please? As in, why do you need to do:

.unMatched(request().get("*.html")).willReturn(needToKnowUrl())

Instead of

service("www.url-i-need-to-know").unmatched(...)

I'm trying to get my head around a situation where you wouldn't want to do everything statically.