Stubbing multiple responses to the same uri

1,516 views
Skip to first unread message

Arvicco

unread,
Jan 18, 2010, 4:39:19 AM1/18/10
to webmock-users
Is there a way to stub several responses in sequence (say, first post
to ***/delete returns delete token, second post returns status). In
FakeWeb, you can do it by registering an array of responses to
specific uri.

What is the WebMock way of doing it? If it is even possible, of course
- since your Readme states: "Always the last declared stub matching
the request will be applied"...

Bartosz Blimke

unread,
Jan 18, 2010, 8:54:07 AM1/18/10
to webmoc...@googlegroups.com
Hi,

There is no way to stub identical request to respond with a sequence of responses in WebMock currently.

This could be easily implemented i.e. by passing an array of response hashes to the to_return() method. 
The only problem is - how should WebMock behave after the last response was returned?
Would it be ok if it returned the last response from a sequence infinitely?

Let me know if you have any thought.

Bartosz


2010/1/18 Arvicco <arvit...@gmail.com>

Arvicco

unread,
Jan 18, 2010, 9:43:28 AM1/18/10
to webmock-users
Thanks for your reply! Yes, this is exactly how it's implemented in
FakeWeb and nobody seems to complain about it. In addition, you can
accept something like :times=>n key in response hash that makes
WebMock return this response exactly n times (again, this is something
FakeWeb does).

Another thing which seems to be missing in WebMock right now, is the
ability to stub whole response from file, not just body (as FakeWeb
does with its :response key). The reason for that, usually you have a
whole set of response stubs collected with "curl -i http://www.example.com/
> response_for_www.example.com". Now, it would be much easier to use
WebMock as an (almost) drop-in replacement for FakeWeb if you are able
to re-use these stub files. Instead of trying to split them all into
body and headers and manually set headers/status in stub_request, it's
much better to be able to say:
stub_request(:any, "www.example.com").to_return(:response => "/
path/to/your/stub/file")

Other than that, WebMock looks like a really great replacement for
FakeWeb with added functionality (like setting expectations for
request body and query params that's not possible currently with
fakeweb_matcher).

On Jan 18, 4:54 pm, Bartosz Blimke <bartosz.bli...@gmail.com> wrote:
> Hi,
>
> There is no way to stub identical request to respond with a sequence of
> responses in WebMock currently.
>
> This could be easily implemented i.e. by passing an array of response hashes
> to the to_return() method.
> The only problem is - how should WebMock behave after the last response was
> returned?
> Would it be ok if it returned the last response from a sequence infinitely?
>
> Let me know if you have any thought.
>
> Bartosz
>

> 2010/1/18 Arvicco <arvitall...@gmail.com>

Bartosz Blimke

unread,
Jan 19, 2010, 6:27:13 PM1/19/10
to webmoc...@googlegroups.com
I'm planning to add multiple responses with the following syntax:

 stub_request(:any, "www.example.com").to_return({ :body => "abc"}, { :body => "def" }).then.to_return({ :body => "ghj" }).then.to_raise(Error)

Is "times" functionality useful? If yes, then it could be possibly added with syntax like:

stub_request(:any, "www.example.com").to_return({ :body => "abc"}).times(5).then.to_return({ :body => "ghj" })

the only problem is, what should be returned as the 6th response, for the following stub:

 stub_request(:any, "www.example.com").to_return({ :body => "abc"}, { :body => "def" }).times(5).then.to_return({ :body => "ghj" }) ?


Replying responses recorded with curl sound like a useful feature. I would add support using the following syntax:
stub_request(:any, "www.example.com").to_return(File.new("response_for_www.example.com"))

and

response = File.new("response_for_www.example.com").read
stub_request(:any, "www.example.com").to_return(response)


Bartosz


2010/1/18 Arvicco <arvit...@gmail.com>

Arvicco

unread,
Jan 20, 2010, 2:52:00 AM1/20/10
to webmock-users
Bartosz,

Proposed interface for curl-gathered responses looks good to me,
indeed in makes sense when you're providing the whole response
(either from file or from string).

Regarding syntax for multiple responses, I totally understand your
desire
to provide fluent interfaces. However it will be much more difficult
to stub
multiple responses programmatically with this approach. Instead of
pushing all the necessary response hashes into array (adding :times
key as necessary) and then stubbing the whole set of responses in
one concise sentence, now you'll have to.. do what? construct a mile-
long
method chain or "times(n).then.to_return({...}).times(m).then.to_return
(..." ?
While improving readability somewhat in case of short response chains
(2-3 responses) in will become so unwieldy with longer response
chains... :(

You have noticed another problem with such interface("what should be
returned
as the 6th response?") resulting from :times directive not being part
of response
hash, but rather a separate member of method chain.

On Jan 20, 2:27 am, Bartosz Blimke <bartosz.bli...@gmail.com> wrote:
> I'm planning to add multiple responses with the following syntax:
>
>  stub_request(:any, "www.example.com").to_return({ :body => "abc"}, { :body
> => "def" }).then.to_return({ :body => "ghj" }).then.to_raise(Error)
>
> Is "times" functionality useful? If yes, then it could be possibly added
> with syntax like:
>
> stub_request(:any, "www.example.com").to_return({ :body =>
> "abc"}).times(5).then.to_return({ :body => "ghj" })
>
> the only problem is, what should be returned as the 6th response, for the
> following stub:
>
>  stub_request(:any, "www.example.com").to_return({ :body => "abc"}, { :body
> => "def" }).times(5).then.to_return({ :body => "ghj" }) ?
>
> Replying responses recorded with curl sound like a useful feature. I would
> add support using the following syntax:
>

> curl -ihttp://www.example.com/> response_for_www.example.com


>
> stub_request(:any, "www.example.com").to_return(File.new("
> response_for_www.example.com"))
>
> and
>
> response = File.new("response_for_www.example.com").read
> stub_request(:any, "www.example.com").to_return(response)
>
> Bartosz
>

> 2010/1/18 Arvicco <arvitall...@gmail.com>

Bartosz Blimke

unread,
Jan 20, 2010, 4:50:10 AM1/20/10
to webmoc...@googlegroups.com
I would suspect that rarely anyone would use long chains (but I may be mistaken). I usually stub requests with a single response, but your specific case can be different. I do agree that fluent interfaces can significantly reduce readability if chains are too long. 
One thing I don't like, is mixing :times attribute in the response with other response specific attributs. While :body and :headers are obviously part of the response model, :times is not.

Another problem is, how to define a stub that first returns response and on the next request raises exception. It's easy with to_return(...).then.to_raise(...). I again don't like the idea of mixing :exception attribute inside response, where it doesn't seem to belong.

Ant thoughts?

Bartosz

2010/1/20 Arvicco <arvit...@gmail.com>

Arvicco

unread,
Jan 21, 2010, 6:43:06 AM1/21/10
to webmock-users
Yes, I understand your logic - :times and :exception are external to
response
object so merging them into response hash is a bit weird given the
idiom
that you're employing - stub_request().to_return().

My use case, however, requires stubbing a long response chains to the
same
uri (with different request bodies). Array of hashes seems to work
best in this case...

At the same time, I understand value of fluent interface for short
response chains.

Well, maybe you shouldn't try to lump together these two different
use
cases, and make separate method in addition to #to_return that
specifically accepts array of hashes used for building responses?
Say, stub_request().with([]) - or some better name, I'm not really
good
at constructing fluent interfaces. ;)

On Jan 20, 12:50 pm, Bartosz Blimke <bartosz.bli...@gmail.com> wrote:
> I would suspect that rarely anyone would use long chains (but I may be
> mistaken). I usually stub requests with a single response, but your specific
> case can be different. I do agree that fluent interfaces can significantly
> reduce readability if chains are too long.
> One thing I don't like, is mixing :times attribute in the response with
> other response specific attributs. While :body and :headers are obviously
> part of the response model, :times is not.
>
> Another problem is, how to define a stub that first returns response and on
> the next request raises exception. It's easy with
> to_return(...).then.to_raise(...). I again don't like the idea of mixing
> :exception attribute inside response, where it doesn't seem to belong.
>
> Ant thoughts?
>
> Bartosz
>

> 2010/1/20 Arvicco <arvitall...@gmail.com>

Niels Meersschaert

unread,
Jan 21, 2010, 9:40:56 AM1/21/10
to webmoc...@googlegroups.com
I am also having to setup multiple request bodies with same url that map to different response bodies (typical SOAP). However, the approach I've taken was one of multiple stub_requests, even though they have duplicate urls. Bartosz just committed to master an option to have a regex for the body, which might simplify situations with that. However, with multiple responses/requests per URI, I think it is a slightly different paradigm. Perhaps a slightly different entry so we don't cloud stub_request:

What we want to do is re-use the same uri, but offer multiple bodies/headers to match to multiple responses.

stub_uri(:uri => "", :request_mappings => [{:body => "MY SOAP REQUEST", :headers => "MY SOAP ACTION} => {:body => "MY SOAP RESPONSE", :status => 200, :headers => "MY SOAP RESPONSE HEADERS}, ...])

Niels

Bartosz Blimke

unread,
Jan 30, 2010, 7:12:25 PM1/30/10
to webmock-users
Hi Niels,

I just pushed new feature to master, pulled from Sergio Gill's fork.
It allows matching requests using a block provided. I think it could
be useful for matching complex SOAP requests.
i.e.

stub_http_request(:get, "www.example.com").with { |request|
request.body == "wadus" }

request(:get, "www.example.com").with { |req| req.body ==
"wadus" }.should have_been_made

Bartosz

Bartosz Blimke

unread,
Jan 30, 2010, 7:39:08 PM1/30/10
to webmoc...@googlegroups.com
I was thinking how to solve this problem and I haven't come up with a nice idea yet. Probably as you said,
it will be better to create separate interface to support mapping of multiple requests to multiple responses.

For now I decided to support to_return method to accept multiple responses, i.e.

stub_request(:get, "www.example.com").to_return([ {:body => "1"}, {:body => "2"}, {:body => "3"} ])

I also added support for chaining to_return and to_raise statements i.e.

 stub_request(:get, "www.example.com").
              to_return({:body => "1"}).then.
              to_return({:body => "2"}).then.
              to_return({:body => "3"})

then() is just a syntactic sugar.

I also added times(N) method wich allows specifying how many times given response should be returned, before returning the next one. i.e

  stub_request(:get, "www.example.com").
        to_return({:body => "1"}).times(2).
        to_return({:body => "2"})

I know that it may have created a confusion regarding what should be returned as 3rd response in a row in the following case:

 stub_request(:get, "www.example.com").
        to_return({:body => "1"}, {:body => "2"}).times(2).
        to_return({:body => "3"})

times(2) applies to the whole to_return, so as the 3rd response in a row, body "1" will be returned then again body "2" and then body "3". I doubt that anyone will be using this case anyway.


The above features do not really help in your case, with multiple responses for multiple different requests and the same uri.
It should be fairly easy on the other side to implement custom interface for what you need, and just wrap calls to existing methods. 
I.e example stub_uri method, Niels provided, could be implemented as a wrapper for a sequence of stub_request calls.

def stub_uri(uri, request_mappings)
  request_mappings.each |request, response| do
    stub_request(:get, uri).with(request).to_return(response)
  end
end

If you have any ideas how this feature could be nicely implemented in WebMock, let me know.

Bartosz

2010/1/21 Niels Meersschaert <nmeers...@mac.com>

Arvicco

unread,
Feb 2, 2010, 4:46:04 AM2/2/10
to webmock-users
Bartosz,

Looked into new version you pushed, seems to be even cooler than the
previous one. Seems like it'll help you to write really expressive
specs...

A bit confused about your suggestion for stub_uri wrapper though... :(
Your README says this:
----


Always the last declared stub matching the request will be applied

i.e:


stub_request(:get, "www.example.com").to_return(:body =>

"abc")


stub_request(:get, "www.example.com").to_return(:body =>

"def")
Net::HTTP.get('www.example.com', '/') # ====> "def"
----

This seems to imply that stubbing multiple request_mappings via
stub_request
is not an option, as the last declared stub will be always returned..
Is this not
the case, or am I missing something here?

Also, it would be interesting to see how expectations on requests
sequence could
be set... Is there some way to set expectations for a specific
(ordered) sequence
of requests? It seems that currently there is no way to make following
expectations
order-sensitive, that is failing if order is def->abc instead of abc-
>def. Any suggestions?

WebMock.should have_requested(:get, "www.example.com").with(:body =>
"abc"})
WebMock.should have_requested(:get, "www.example.com").with(:body =>
"def"})

Arvicco

Bartosz Blimke

unread,
Feb 2, 2010, 9:23:05 AM2/2/10
to webmoc...@googlegroups.com

This seems to imply that stubbing multiple request_mappings via
stub_request
is not an option, as the last declared stub will be always returned..
Is this not
the case, or am I missing something here?

 
You need to distinguish the requests somehow using with(). In the stub_uri method
example I provided the assumption is that the request parameters are different and unique between requests.

If the request params are the same then you would have to collect all entries with the same request
parameters and create a stub with multiple responses.
 
Also, it would be interesting to see how expectations on requests
sequence could
be set... Is there some way to set expectations for a specific
(ordered) sequence
of requests? 

There is no way to specify expectations on the order of requests, but I think it should be possible,
so if you think it would be a useful feature or even have an idea how the syntax should look, please create an issue in github.

Regarding the example you provided, it looks like a bug. I will try to reproduce it and investigate it today.

Cheers,

Bartosz

Bartosz Blimke

unread,
Feb 2, 2010, 9:31:25 AM2/2/10
to webmoc...@googlegroups.com

Regarding the example you provided, it looks like a bug. I will try to reproduce it and investigate it today.

 Ahh.. sorry I misread this. So currently expectations are order insensitive, there is no bug. 
Let me know if you have any suggestions regarding syntax to support ordering.

Bartosz


Reply all
Reply to author
Forward
0 new messages