Skip to content

Commit

Permalink
Allow requests to be stubbed by external libraries (e.g. WebMock or F…
Browse files Browse the repository at this point in the history
…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 vcr#146.
  • Loading branch information
myronmarston committed May 29, 2012
1 parent 42babb2 commit 499646a
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -39,6 +39,8 @@
[fixed in WebMock](https://github.com/bblimke/webmock/pull/185). [fixed in WebMock](https://github.com/bblimke/webmock/pull/185).
Thanks to [Mark Abramov](https://github.com/markiz) for reporting Thanks to [Mark Abramov](https://github.com/markiz) for reporting
the bug and providing a great example test case. 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) ## 2.1.1 (April 24, 2012)


Expand Down
2 changes: 1 addition & 1 deletion features/configuration/debug_logging.feature
Expand Up @@ -47,6 +47,6 @@ Feature: Debug Logging
[Cassette: 'example'] method (matched): current request [get http://localhost:7777/] vs [get http://localhost:7777/] [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'] 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"] [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/]
""" """
2 changes: 1 addition & 1 deletion lib/vcr/library_hooks/excon.rb
Expand Up @@ -23,7 +23,7 @@ def handle


private private


def on_stubbed_request def on_stubbed_by_vcr_request
@vcr_response = stubbed_response @vcr_response = stubbed_response
{ {
:body => stubbed_response.body, :body => stubbed_response.body,
Expand Down
17 changes: 15 additions & 2 deletions lib/vcr/library_hooks/fakeweb.rb
Expand Up @@ -40,6 +40,10 @@ def already_seen_requests


private private


def externally_stubbed?
::FakeWeb.registered_uri?(request_method, uri)
end

def request_type(*args) def request_type(*args)
@request_type || super @request_type || super
end end
Expand All @@ -64,11 +68,16 @@ def invoke_after_request_hook(vcr_response)
super super
end end


def on_externally_stubbed_request
# just perform the request--FakeWeb will handle it
perform_request(:started)
end

def on_recordable_request def on_recordable_request
perform_request(net_http.started?, :record_interaction) perform_request(net_http.started?, :record_interaction)
end end


def on_stubbed_request def on_stubbed_by_vcr_request
with_exclusive_fakeweb_stub(stubbed_response) do with_exclusive_fakeweb_stub(stubbed_response) do
# force it to be considered started since it doesn't # force it to be considered started since it doesn't
# recurse in this case like the others. # recurse in this case like the others.
Expand Down Expand Up @@ -124,9 +133,13 @@ def with_exclusive_fakeweb_stub(response)
end end
end end


def request_method
request.method.downcase.to_sym
end

def vcr_request def vcr_request
@vcr_request ||= VCR::Request.new \ @vcr_request ||= VCR::Request.new \
request.method.downcase.to_sym, request_method,
uri, uri,
(request_body || request.body), (request_body || request.body),
request.to_hash request.to_hash
Expand Down
6 changes: 5 additions & 1 deletion lib/vcr/library_hooks/typhoeus.rb
Expand Up @@ -25,6 +25,10 @@ def vcr_request


private private


def externally_stubbed?
::Typhoeus::Hydra.stubs.detect { |stub| stub.matches?(request) }
end

def set_typed_request_for_after_hook(*args) def set_typed_request_for_after_hook(*args)
super super
request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request) request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
Expand All @@ -35,7 +39,7 @@ def on_unhandled_request
super super
end end


def on_stubbed_request def on_stubbed_by_vcr_request
::Typhoeus::Response.new \ ::Typhoeus::Response.new \
:http_version => stubbed_response.http_version, :http_version => stubbed_response.http_version,
:code => stubbed_response.status.code, :code => stubbed_response.status.code,
Expand Down
33 changes: 31 additions & 2 deletions lib/vcr/library_hooks/webmock.rb
Expand Up @@ -8,6 +8,21 @@ module VCR
class LibraryHooks class LibraryHooks
# @private # @private
module WebMock 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 # @private
module Helpers module Helpers
def vcr_request_for(webmock_request) def vcr_request_for(webmock_request)
Expand Down Expand Up @@ -71,6 +86,13 @@ def initialize(request)


private 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) def set_typed_request_for_after_hook(*args)
super super
request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request) request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
Expand All @@ -80,12 +102,17 @@ def vcr_request
@vcr_request ||= vcr_request_for(request) @vcr_request ||= vcr_request_for(request)
end end


def on_externally_stubbed_request
# nil allows WebMock to handle the request
nil
end

def on_unhandled_request def on_unhandled_request
invoke_after_request_hook(nil) invoke_after_request_hook(nil)
super super
end end


def on_stubbed_request def on_stubbed_by_vcr_request
{ {
:body => stubbed_response.body, :body => stubbed_response.body,
:status => [stubbed_response.status.code.to_i, stubbed_response.status.message], :status => [stubbed_response.status.code.to_i, stubbed_response.status.message],
Expand All @@ -96,7 +123,9 @@ def on_stubbed_request


extend Helpers 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| ::WebMock.after_request(:real_requests_only => true) do |request, response|
unless VCR.library_hooks.disabled?(:webmock) unless VCR.library_hooks.disabled?(:webmock)
Expand Down
2 changes: 1 addition & 1 deletion lib/vcr/middleware/faraday.rb
Expand Up @@ -77,7 +77,7 @@ def on_ignored_request
app.call(env) app.call(env)
end end


def on_stubbed_request def on_stubbed_by_vcr_request
headers = env[:response_headers] ||= ::Faraday::Utils::Headers.new headers = env[:response_headers] ||= ::Faraday::Utils::Headers.new
headers.update stubbed_response.headers if stubbed_response.headers headers.update stubbed_response.headers if stubbed_response.headers
env.update :status => stubbed_response.status.code, :body => stubbed_response.body env.update :status => stubbed_response.status.code, :body => stubbed_response.body
Expand Down
12 changes: 10 additions & 2 deletions lib/vcr/request_handler.rb
Expand Up @@ -32,8 +32,9 @@ def set_typed_request_for_after_hook(request_type)


def request_type(consume_stub = false) def request_type(consume_stub = false)
case case
when externally_stubbed? then :externally_stubbed
when should_ignore? then :ignored 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 when VCR.real_http_connections_allowed? then :recordable
else :unhandled else :unhandled
end end
Expand All @@ -50,6 +51,10 @@ def invoke_after_request_hook(vcr_response)
VCR.configuration.invoke_hook(:after_http_request, @after_hook_typed_request, vcr_response) VCR.configuration.invoke_hook(:after_http_request, @after_hook_typed_request, vcr_response)
end end


def externally_stubbed?
false
end

def should_ignore? def should_ignore?
disabled? || VCR.request_ignorer.ignore?(vcr_request) disabled? || VCR.request_ignorer.ignore?(vcr_request)
end end
Expand All @@ -76,10 +81,13 @@ def library_name
end end


# Subclasses can implement these # Subclasses can implement these
def on_externally_stubbed_request
end

def on_ignored_request def on_ignored_request
end end


def on_stubbed_request def on_stubbed_by_vcr_request
end end


def on_recordable_request def on_recordable_request
Expand Down
24 changes: 21 additions & 3 deletions lib/vcr/structs.rb
Expand Up @@ -254,9 +254,19 @@ def ignored?
type == :ignored type == :ignored
end end


# @return [Boolean] whether or not this request will be stubbed # @return [Boolean] whether or not this request is being stubbed by VCR
def stubbed? # @see #externally_stubbed?
type == :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 end


# @return [Boolean] whether or not this request will be recorded. # @return [Boolean] whether or not this request will be recorded.
Expand All @@ -275,6 +285,14 @@ def real?
ignored? || recordable? ignored? || recordable?
end 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 undef method
end end


Expand Down
32 changes: 31 additions & 1 deletion spec/support/shared_example_groups/hook_into_http_library.rb
Expand Up @@ -144,6 +144,28 @@ def self.test_playback(description, url)
directly_stub_request(:get, request_url, "stubbed response") directly_stub_request(:get, request_url, "stubbed response")
get_body_string(make_http_request(:get, request_url)).should eq("stubbed response") get_body_string(make_http_request(:get, request_url)).should eq("stubbed response")
end 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
end end


Expand Down Expand Up @@ -306,6 +328,14 @@ def self.test_playback(description, url)
it_behaves_like "request hooks", library_hook_name, :ignored it_behaves_like "request hooks", library_hook_name, :ignored
end 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 context 'when the request is recorded' do
let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') } let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') }


Expand Down Expand Up @@ -341,7 +371,7 @@ def self.test_playback(description, url)
stub_requests([http_interaction(request_url)], [:method, :uri]) stub_requests([http_interaction(request_url)], [:method, :uri])
end end


it_behaves_like "request hooks", library_hook_name, :stubbed it_behaves_like "request hooks", library_hook_name, :stubbed_by_vcr
end end


context 'when the request is not allowed' do context 'when the request is not allowed' do
Expand Down
4 changes: 2 additions & 2 deletions spec/vcr/configuration_spec.rb
Expand Up @@ -213,8 +213,8 @@ def request(type)


it 'handles symbol request predicate filters properly' do it 'handles symbol request predicate filters properly' do
yielded = false yielded = false
subject.after_http_request(:stubbed?) { |req| yielded = true } subject.after_http_request(:stubbed_by_vcr?) { |req| yielded = true }
subject.invoke_hook(:after_http_request, request(:stubbed), response) subject.invoke_hook(:after_http_request, request(:stubbed_by_vcr), response)
yielded.should be_true yielded.should be_true


yielded = false yielded = false
Expand Down
23 changes: 20 additions & 3 deletions spec/vcr/structs_spec.rb
Expand Up @@ -386,7 +386,8 @@ def assert_yielded_keys(hash, *keys)
end end
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 describe "##{type}?" do
it "returns true if the type is set to :#{type}" do it "returns true if the type is set to :#{type}" do
Request::Typed.new(stub, type).send("#{type}?").should be_true Request::Typed.new(stub, type).send("#{type}?").should be_true
Expand All @@ -399,18 +400,34 @@ def assert_yielded_keys(hash, *keys)
end end


describe "#real?" do 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 it "returns true if the type is set to :#{type}" do
Request::Typed.new(stub, type).should be_real Request::Typed.new(stub, type).should be_real
end end
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 it "returns false if the type is set to :#{type}" do
Request::Typed.new(stub, type).should_not be_real Request::Typed.new(stub, type).should_not be_real
end end
end 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 end


describe Request do describe Request do
Expand Down

0 comments on commit 499646a

Please sign in to comment.