Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Adding support for lazy construction of responses from stubbed requests #275

Merged
merged 2 commits into from

2 participants

@ryankinderman

See new example doc entries on Typhoeus::Expectation for how this can be used.

Note:

  • With the introduction of deferred response construction, the creation of a response for a request stub should be able to make assumptions about the request insofar as it matches the stub. For example, if the stub specifies that the request method is a GET, then the block that constructs the response should be able to assume that it's getting called for a GET request.
  • To reduce the possibility that a response construction block is called with an unmatching request, I've replaced Typhoeus::Expectation.find_by with .response_for, to provide the *::Stubbable subclasses with a single method to call to obtain a stubbed response, if there are any that match the request. This is in an effort to emphasize that an expectation's response is based on the corresponding request matching the stub specification.
@i0rek
Owner

Thank you very much for your work! I'll look into it either today or tomorrow!

@i0rek
Owner

@ryankinderman I finally have time to have a look at your work and I love it! :rocket:
What do you think about doing the same like rspec does in here: https://www.relishapp.com/rspec/rspec-mocks/v/2-13/docs/message-expectations/expect-a-message#provide-a-return-value regarding the block syntax?

Typhoeus.stub("www.example.com/widgets").and_return(crafted_response)
# or
Typhoeus.stub("www.example.com/widgets") do |request|
  Typhoeus::Response.new(body: request.url)
end
@ryankinderman

The latter is fine with me as well. If you prefer that, I can update it.

@i0rek
Owner

That would be awesome!

@ryankinderman

Okay, the solution now supports:

Typhoeus.stub(...) do |request|
  #... build response ...
end

I kept the functionality for adding the block to the expectation on Expectation#and_return, since that needs to happen somewhere anyways. Not sure if you want to move that to a separate method that's not part of the public API; if so, let me know, but I personally think it's fine where it is.

@i0rek
Owner

awesome!

@i0rek i0rek merged commit c53f026 into typhoeus:master

1 check passed

Details default The Travis build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
12 lib/typhoeus.rb
@@ -73,13 +73,15 @@ def configure
# @return [ Typhoeus::Expectation ] The expecatation.
#
# @see Typhoeus::Expectation
- def stub(base_url, options = {})
+ def stub(base_url, options = {}, &block)
expectation = Expectation.all.find{ |e| e.base_url == base_url && e.options == options }
- return expectation if expectation
-
- Expectation.new(base_url, options).tap do |new_expectation|
- Expectation.all << new_expectation
+ if expectation.nil?
+ expectation = Expectation.new(base_url, options)
+ Expectation.all << expectation
end
+
+ expectation.and_return &block unless block.nil?
+ expectation
end
# Add before callbacks.
View
54 lib/typhoeus/expectation.rb
@@ -14,6 +14,30 @@ module Typhoeus
# actual = Typhoeus.get("www.example.com")
# expected == actual
# #=> true
+ #
+ # @example Stub a request and get a lazily-constructed response containing data from actual widgets that exist in the system when the stubbed request is made.
+ # Typhoeus.stub("www.example.com/widgets") do
+ # actual_widgets = Widget.all
+ # Typhoeus::Response.new(
+ # :body => actual_widgets.inject([]) do |ids, widget|
+ # ids << widget.id
+ # end.join(",")
+ # )
+ # end
+ #
+ # @example Stub a request and get a lazily-constructed response in the format requested.
+ # Typhoeus.stub("www.example.com") do |request|
+ # accept = (request.options[:headers]||{})['Accept'] || "application/json"
+ # format = accept.split(",").first
+ # body_obj = { 'things' => [ { 'id' => 'foo' } ] }
+ #
+ # Typhoeus::Response.new(
+ # :headers => {
+ # 'Content-Type' => format
+ # },
+ # :body => SERIALIZERS[format].serialize(body_obj)
+ # )
+ # end
class Expectation
# @api private
@@ -47,15 +71,26 @@ def clear
all.clear
end
- # Returns expecation matching the provided
- # request.
+ # Returns stubbed response matching the
+ # provided request
#
- # @example Find expectation.
- # Typhoeus::Expectation.find_by(request)
+ # @example Find response
+ # Typhoeus::Expectation.response_for(request)
#
- # @return [ Expectation ] The matching expectation.
+ # @return [ Typhoeus::Response ] The stubbed response from a
+ # matching expectation, or nil if no matching expectation
+ # is found.
#
# @api private
+ def response_for(request)
+ expectation = find_by(request)
+ return nil if expectation.nil?
+
+ expectation.response(request)
+ end
+
+ private
+
def find_by(request)
all.find do |expectation|
expectation.matches?(request)
@@ -101,8 +136,8 @@ def stubbed_from(value)
# expectation.and_return(response)
#
# @return [ void ]
- def and_return(response)
- responses << response
+ def and_return(response=nil, &block)
+ responses << (response.nil? ? block : response)
end
# Checks wether this expectation matches
@@ -142,8 +177,11 @@ def responses
# @return [ Response ] The response.
#
# @api private
- def response
+ def response(request)
response = responses.fetch(@response_counter, responses.last)
+ if response.respond_to?(:call)
+ response = response.call(request)
+ end
@response_counter += 1
response.mock = @from || true
response
View
4 lib/typhoeus/hydra/stubbable.rb
@@ -15,8 +15,8 @@ module Stubbable
# @example Add the request.
# hydra.add(request)
def add(request)
- if expectation = Expectation.find_by(request)
- request.finish(expectation.response)
+ if response = Expectation.response_for(request)
+ request.finish(response)
else
super
end
View
4 lib/typhoeus/request/stubbable.rb
@@ -17,8 +17,8 @@ module Stubbable
#
# @return [ Response ] The response.
def run
- if expectation = Expectation.find_by(self)
- finish(expectation.response)
+ if response = Expectation.response_for(self)
+ finish(response)
else
super
end
View
51 spec/typhoeus/expectation_spec.rb
@@ -47,13 +47,23 @@
end
end
- describe ".find_by" do
+ describe ".response_for" do
let(:request) { Typhoeus::Request.new("") }
+ let(:stubbed_response) { Typhoeus::Response.new }
- it "returns a dummy when expectations not empty" do
+ it "finds a matching expectation and returns its next response" do
Typhoeus::Expectation.all << expectation
expectation.should_receive(:matches?).with(request).and_return(true)
- expect(Typhoeus::Expectation.find_by(request)).to eq(expectation)
+ expectation.should_receive(:response).with(request).and_return(stubbed_response)
+
+ response = Typhoeus::Expectation.response_for(request)
+
+ expect(response).to be(stubbed_response)
+ end
+
+ it "returns nil if no matching expectation is found" do
+ response = Typhoeus::Expectation.response_for(request)
+ expect(response).to be(nil)
end
end
@@ -83,6 +93,14 @@
expect(expectation.responses).to eq([1, 2])
end
end
+
+ context "when block" do
+ it "adds to responses" do
+ block = Proc.new {}
+ expectation.and_return &block
+ expect(expectation.responses).to eq([block])
+ end
+ end
end
describe "#responses" do
@@ -92,13 +110,32 @@
end
describe "#response" do
+ let(:request) { Typhoeus::Request.new("") }
+
before { expectation.instance_variable_set(:@responses, responses) }
context "when one response" do
- let(:responses) { [Typhoeus::Response.new] }
+ context "is pre-constructed" do
+ let(:responses) { [Typhoeus::Response.new] }
+
+ it "returns response" do
+ expect(expectation.response(request)).to be(responses[0])
+ end
+ end
- it "returns response" do
- expect(expectation.response).to be(responses[0])
+ context "is lazily-constructed" do
+ def construct_response(request)
+ @request_from_response_construction = request
+ lazily_constructed_response
+ end
+
+ let(:lazily_constructed_response) { Typhoeus::Response.new }
+ let(:responses) { [ Proc.new { |request| construct_response(request) } ] }
+
+ it "returns response" do
+ expect(expectation.response(request)).to be(lazily_constructed_response)
+ expect(@request_from_response_construction).to be(request)
+ end
end
end
@@ -107,7 +144,7 @@
it "returns one by one" do
3.times do |i|
- expect(expectation.response).to eq(responses[i])
+ expect(expectation.response(request)).to be(responses[i])
end
end
end
View
12 spec/typhoeus/hydra/before_spec.rb
@@ -16,7 +16,7 @@
context "when true" do
it "calls super" do
Typhoeus.before { true }
- Typhoeus::Expectation.should_receive(:find_by)
+ Typhoeus::Expectation.should_receive(:response_for)
hydra.add(request)
end
end
@@ -24,7 +24,7 @@
context "when false" do
it "doesn't call super" do
Typhoeus.before { false }
- Typhoeus::Expectation.should_receive(:find_by).never
+ Typhoeus::Expectation.should_receive(:response_for).never
hydra.add(request)
end
end
@@ -32,7 +32,7 @@
context "when response" do
it "doesn't call super" do
Typhoeus.before { Typhoeus::Response.new }
- Typhoeus::Expectation.should_receive(:find_by).never
+ Typhoeus::Expectation.should_receive(:response_for).never
hydra.add(request)
end
end
@@ -43,7 +43,7 @@
before { 3.times { Typhoeus.before { |r| String.new(r.base_url) } } }
it "calls super" do
- Typhoeus::Expectation.should_receive(:find_by)
+ Typhoeus::Expectation.should_receive(:response_for)
hydra.add(request)
end
@@ -61,7 +61,7 @@
end
it "doesn't call super" do
- Typhoeus::Expectation.should_receive(:find_by).never
+ Typhoeus::Expectation.should_receive(:response_for).never
hydra.add(request)
end
@@ -75,7 +75,7 @@
context "when no before" do
it "calls super" do
- Typhoeus::Expectation.should_receive(:find_by)
+ Typhoeus::Expectation.should_receive(:response_for)
hydra.add(request)
end
end
View
2  spec/typhoeus/hydra/stubbable_spec.rb
@@ -9,7 +9,7 @@
before { Typhoeus.stub(base_url).and_return(response) }
describe "#add" do
- it "checks expactations" do
+ it "checks expectations" do
hydra.add(request)
end
View
12 spec/typhoeus/request/before_spec.rb
@@ -15,7 +15,7 @@
context "when true" do
it "calls super" do
Typhoeus.before { true }
- Typhoeus::Expectation.should_receive(:find_by)
+ Typhoeus::Expectation.should_receive(:response_for)
request.run
end
end
@@ -23,7 +23,7 @@
context "when false" do
it "doesn't call super" do
Typhoeus.before { false }
- Typhoeus::Expectation.should_receive(:find_by).never
+ Typhoeus::Expectation.should_receive(:response_for).never
request.run
end
@@ -36,7 +36,7 @@
context "when a response" do
it "doesn't call super" do
Typhoeus.before { Typhoeus::Response.new }
- Typhoeus::Expectation.should_receive(:find_by).never
+ Typhoeus::Expectation.should_receive(:response_for).never
request.run
end
@@ -52,7 +52,7 @@
before { 3.times { Typhoeus.before { |r| String.new(r.base_url) } } }
it "calls super" do
- Typhoeus::Expectation.should_receive(:find_by)
+ Typhoeus::Expectation.should_receive(:response_for)
request.run
end
@@ -70,7 +70,7 @@
end
it "doesn't call super" do
- Typhoeus::Expectation.should_receive(:find_by).never
+ Typhoeus::Expectation.should_receive(:response_for).never
request.run
end
@@ -84,7 +84,7 @@
context "when no before" do
it "calls super" do
- Typhoeus::Expectation.should_receive(:find_by)
+ Typhoeus::Expectation.should_receive(:response_for)
request.run
end
end
View
4 spec/typhoeus/request/stubbable_spec.rb
@@ -7,8 +7,8 @@
before { Typhoeus.stub(base_url).and_return(response) }
- describe "#queue" do
- it "checks expactations" do
+ describe "#run" do
+ it "checks expectations" do
request.run
end
View
17 spec/typhoeus_spec.rb
@@ -21,7 +21,22 @@
describe ".stub" do
let(:base_url) { "www.example.com" }
+ shared_examples "lazy response construction" do
+ it "calls the block to construct a response when a request matches the stub" do
+ expected_response = Typhoeus::Response.new
+ Typhoeus.stub(base_url) do |request|
+ expected_response
+ end
+
+ response = Typhoeus.get(base_url)
+
+ expect(response).to be(expected_response)
+ end
+ end
+
context "when no similar expectation exists" do
+ include_examples "lazy response construction"
+
it "returns expectation" do
expect(Typhoeus.stub(base_url)).to be_a(Typhoeus::Expectation)
end
@@ -33,6 +48,8 @@
end
context "when similar expectation exists" do
+ include_examples "lazy response construction"
+
let(:expectation) { Typhoeus::Expectation.new(base_url) }
before { Typhoeus::Expectation.all << expectation }
Something went wrong with that request. Please try again.