Permalink
Browse files

Allow requests to be stubbed by external libraries (e.g. WebMock or F…

…akeWeb).

Previously, direct WebMock/FakeWeb/Typhoeus stubs would be ignored
unless VCR was turned off. However, some users have requested that
VCR use these stubs if they are set. This enables that.

Part of this changes the VCR::Request::Typed interface a little bit;
#stubbed? now returns true regardless of how it was stubbed, and
#stubbed_by_vcr? should be used to test if VCR stubbed it (vs.
#externally_stubbed?).

Note that I consider this a bit experimental. I'm not sure I've thought
through all the repercussions of this, and I consider this only
"semi supported".

Closes #146.
  • Loading branch information...
1 parent 42babb2 commit 499646a2662905c51fc16aed54ef91493f9fcccd @myronmarston committed May 29, 2012
View
@@ -39,6 +39,8 @@
[fixed in WebMock](https://github.com/bblimke/webmock/pull/185).
Thanks to [Mark Abramov](https://github.com/markiz) for reporting
the bug and providing a great example test case.
+* Allow requests to be stubbed by external libraries (e.g. WebMock,
+ FakeWeb or Typhoeus) without needing to turn VCR off.
## 2.1.1 (April 24, 2012)
@@ -47,6 +47,6 @@ Feature: Debug Logging
[Cassette: 'example'] method (matched): current request [get http://localhost:7777/] vs [get http://localhost:7777/]
[Cassette: 'example'] uri (matched): current request [get http://localhost:7777/] vs [get http://localhost:7777/]
[Cassette: 'example'] Found matching interaction for [get http://localhost:7777/] at index 0: [200 "Hello World"]
- [fakeweb] Identified request type (stubbed) for [get http://localhost:7777/]
+ [fakeweb] Identified request type (stubbed_by_vcr) for [get http://localhost:7777/]
"""
@@ -23,7 +23,7 @@ def handle
private
- def on_stubbed_request
+ def on_stubbed_by_vcr_request
@vcr_response = stubbed_response
{
:body => stubbed_response.body,
@@ -40,6 +40,10 @@ def already_seen_requests
private
+ def externally_stubbed?
+ ::FakeWeb.registered_uri?(request_method, uri)
+ end
+
def request_type(*args)
@request_type || super
end
@@ -64,11 +68,16 @@ def invoke_after_request_hook(vcr_response)
super
end
+ def on_externally_stubbed_request
+ # just perform the request--FakeWeb will handle it
+ perform_request(:started)
+ end
+
def on_recordable_request
perform_request(net_http.started?, :record_interaction)
end
- def on_stubbed_request
+ def on_stubbed_by_vcr_request
with_exclusive_fakeweb_stub(stubbed_response) do
# force it to be considered started since it doesn't
# recurse in this case like the others.
@@ -124,9 +133,13 @@ def with_exclusive_fakeweb_stub(response)
end
end
+ def request_method
+ request.method.downcase.to_sym
+ end
+
def vcr_request
@vcr_request ||= VCR::Request.new \
- request.method.downcase.to_sym,
+ request_method,
uri,
(request_body || request.body),
request.to_hash
@@ -25,6 +25,10 @@ def vcr_request
private
+ def externally_stubbed?
+ ::Typhoeus::Hydra.stubs.detect { |stub| stub.matches?(request) }
+ end
+
def set_typed_request_for_after_hook(*args)
super
request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
@@ -35,7 +39,7 @@ def on_unhandled_request
super
end
- def on_stubbed_request
+ def on_stubbed_by_vcr_request
::Typhoeus::Response.new \
:http_version => stubbed_response.http_version,
:code => stubbed_response.status.code,
@@ -8,6 +8,21 @@ module VCR
class LibraryHooks
# @private
module WebMock
+ extend self
+
+ attr_accessor :global_hook_disabled
+ alias global_hook_disabled? global_hook_disabled
+
+ def with_global_hook_disabled
+ self.global_hook_disabled = true
+
+ begin
+ yield
+ ensure
+ self.global_hook_disabled = false
+ end
+ end
+
# @private
module Helpers
def vcr_request_for(webmock_request)
@@ -71,6 +86,13 @@ def initialize(request)
private
+ def externally_stubbed?
+ # prevent infinite recursion...
+ VCR::LibraryHooks::WebMock.with_global_hook_disabled do
+ ::WebMock.registered_request?(request)
+ end
+ end
+
def set_typed_request_for_after_hook(*args)
super
request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
@@ -80,12 +102,17 @@ def vcr_request
@vcr_request ||= vcr_request_for(request)
end
+ def on_externally_stubbed_request
+ # nil allows WebMock to handle the request
+ nil
+ end
+
def on_unhandled_request
invoke_after_request_hook(nil)
super
end
- def on_stubbed_request
+ def on_stubbed_by_vcr_request
{
:body => stubbed_response.body,
:status => [stubbed_response.status.code.to_i, stubbed_response.status.message],
@@ -96,7 +123,9 @@ def on_stubbed_request
extend Helpers
- ::WebMock.globally_stub_request { |req| RequestHandler.new(req).handle }
+ ::WebMock.globally_stub_request do |req|
+ global_hook_disabled? ? nil : RequestHandler.new(req).handle
+ end
::WebMock.after_request(:real_requests_only => true) do |request, response|
unless VCR.library_hooks.disabled?(:webmock)
@@ -77,7 +77,7 @@ def on_ignored_request
app.call(env)
end
- def on_stubbed_request
+ def on_stubbed_by_vcr_request
headers = env[:response_headers] ||= ::Faraday::Utils::Headers.new
headers.update stubbed_response.headers if stubbed_response.headers
env.update :status => stubbed_response.status.code, :body => stubbed_response.body
View
@@ -32,8 +32,9 @@ def set_typed_request_for_after_hook(request_type)
def request_type(consume_stub = false)
case
+ when externally_stubbed? then :externally_stubbed
when should_ignore? then :ignored
- when has_response_stub?(consume_stub) then :stubbed
+ when has_response_stub?(consume_stub) then :stubbed_by_vcr
when VCR.real_http_connections_allowed? then :recordable
else :unhandled
end
@@ -50,6 +51,10 @@ def invoke_after_request_hook(vcr_response)
VCR.configuration.invoke_hook(:after_http_request, @after_hook_typed_request, vcr_response)
end
+ def externally_stubbed?
+ false
+ end
+
def should_ignore?
disabled? || VCR.request_ignorer.ignore?(vcr_request)
end
@@ -76,10 +81,13 @@ def library_name
end
# Subclasses can implement these
+ def on_externally_stubbed_request
+ end
+
def on_ignored_request
end
- def on_stubbed_request
+ def on_stubbed_by_vcr_request
end
def on_recordable_request
View
@@ -254,9 +254,19 @@ def ignored?
type == :ignored
end
- # @return [Boolean] whether or not this request will be stubbed
- def stubbed?
- type == :stubbed
+ # @return [Boolean] whether or not this request is being stubbed by VCR
+ # @see #externally_stubbed?
+ # @see #stubbed?
+ def stubbed_by_vcr?
+ type == :stubbed_by_vcr
+ end
+
+ # @return [Boolean] whether or not this request is being stubbed by an
+ # external library (such as WebMock or FakeWeb).
+ # @see #stubbed_by_vcr?
+ # @see #stubbed?
+ def externally_stubbed?
+ type == :externally_stubbed
end
# @return [Boolean] whether or not this request will be recorded.
@@ -275,6 +285,14 @@ def real?
ignored? || recordable?
end
+ # @return [Boolean] whether or not this request will be stubbed.
+ # It may be stubbed by an external library or by VCR.
+ # @see #stubbed_by_vcr?
+ # @see #externally_stubbed?
+ def stubbed?
+ stubbed_by_vcr? || externally_stubbed?
+ end
+
undef method
end
@@ -144,6 +144,28 @@ def self.test_playback(description, url)
directly_stub_request(:get, request_url, "stubbed response")
get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
end
+
+ it 'can directly stub the request when VCR is turned on and no cassette is in use' do
+ directly_stub_request(:get, request_url, "stubbed response")
+ get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
+ end
+
+ it 'can directly stub the request when VCR is turned on and a cassette is in use' do
+ VCR.use_cassette("temp") do
+ directly_stub_request(:get, request_url, "stubbed response")
+ get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
+ end
+ end
+
+ it 'does not record requests that are directly stubbed' do
+ VCR.should respond_to(:record_http_interaction)
+ VCR.should_not_receive(:record_http_interaction)
+
+ VCR.use_cassette("temp") do
+ directly_stub_request(:get, request_url, "stubbed response")
+ get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
+ end
+ end
end
end
@@ -306,6 +328,14 @@ def self.test_playback(description, url)
it_behaves_like "request hooks", library_hook_name, :ignored
end
+ context "when the request is directly stubbed" do
+ before(:each) do
+ directly_stub_request(:get, request_url, "FOO!")
+ end
+
+ it_behaves_like "request hooks", library_hook_name, :externally_stubbed
+ end if method_defined?(:directly_stub_request)
+
context 'when the request is recorded' do
let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') }
@@ -341,7 +371,7 @@ def self.test_playback(description, url)
stub_requests([http_interaction(request_url)], [:method, :uri])
end
- it_behaves_like "request hooks", library_hook_name, :stubbed
+ it_behaves_like "request hooks", library_hook_name, :stubbed_by_vcr
end
context 'when the request is not allowed' do
@@ -213,8 +213,8 @@ def request(type)
it 'handles symbol request predicate filters properly' do
yielded = false
- subject.after_http_request(:stubbed?) { |req| yielded = true }
- subject.invoke_hook(:after_http_request, request(:stubbed), response)
+ subject.after_http_request(:stubbed_by_vcr?) { |req| yielded = true }
+ subject.invoke_hook(:after_http_request, request(:stubbed_by_vcr), response)
yielded.should be_true
yielded = false
View
@@ -386,7 +386,8 @@ def assert_yielded_keys(hash, *keys)
end
end
- [:ignored, :stubbed, :recordable, :unhandled].each do |type|
+ valid_types = [:ignored, :stubbed_by_vcr, :externally_stubbed, :recordable, :unhandled]
+ valid_types.each do |type|
describe "##{type}?" do
it "returns true if the type is set to :#{type}" do
Request::Typed.new(stub, type).send("#{type}?").should be_true
@@ -399,18 +400,34 @@ def assert_yielded_keys(hash, *keys)
end
describe "#real?" do
- [:ignored, :recordable].each do |type|
+ real_types = [:ignored, :recordable]
+ real_types.each do |type|
it "returns true if the type is set to :#{type}" do
Request::Typed.new(stub, type).should be_real
end
end
- [:stubbed, :unhandled].each do |type|
+ (valid_types - real_types).each do |type|
it "returns false if the type is set to :#{type}" do
Request::Typed.new(stub, type).should_not be_real
end
end
end
+
+ describe "#stubbed?" do
+ stubbed_types = [:externally_stubbed, :stubbed_by_vcr]
+ stubbed_types.each do |type|
+ it "returns true if the type is set to :#{type}" do
+ Request::Typed.new(stub, type).should be_stubbed
+ end
+ end
+
+ (valid_types - stubbed_types).each do |type|
+ it "returns false if the type is set to :#{type}" do
+ Request::Typed.new(stub, type).should_not be_stubbed
+ end
+ end
+ end
end
describe Request do

0 comments on commit 499646a

Please sign in to comment.