Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: myronmarston/vcr
...
head fork: myronmarston/vcr
  • 10 commits
  • 18 files changed
  • 0 commit comments
  • 1 contributor
12 CHANGELOG.md
View
@@ -1,5 +1,17 @@
#Changelog
+## 1.1.2 (September 9, 2010)
+
+* Fixed a minor bug with the WebMock integration: WebMock extends each `Net::HTTPResponse` with an extension
+ module after reading the body, and VCR was doing the same thing, leading to some slight deviance from
+ standard Net::HTTP behavior. The fix prevents VCR from adding the same extension to a `Net::HTTPResponse`
+ that has already been extende by WebMock.
+* Fixed a minor bug in the `VCR::Net::HTTPResponse` module so that it correctly handles nil bodies (such as
+ for a HEAD request).
+* Refactored `VCR::Net::HTTPResponse` module so it is implemented in a much simpler manner.
+* Updated specs and features so they pass against the latest WebMock release (1.3.5).
+* Minor documentation updates.
+
## 1.1.1 (August 26, 2010)
* Updated to use and require FakeWeb 1.3.0. It includes a fix for a bug related to multiple values for the
6 Gemfile
View
@@ -4,6 +4,12 @@ gemspec
group :development do
# Our specs rely on something in rspec-core master that hasn't been released yet.
gem 'rspec-core', :git => 'git://github.com/rspec/rspec-core.git'
+
+ # patron and em-http-request can't install on JRuby, so we have to limit their platform here.
+ platforms :ruby do
+ gem 'patron', '~> 0.4.6'
+ gem 'em-http-request', '~> 0.2.7'
+ end
end
# Additional gems that are useful, but not required for development.
9 Gemfile.lock
View
@@ -7,7 +7,7 @@ GIT
PATH
remote: .
specs:
- vcr (1.1.1)
+ vcr (1.1.2)
GEM
remote: http://rubygems.org/
@@ -36,7 +36,6 @@ GEM
addressable (>= 2.0.0)
eventmachine (>= 0.12.9)
eventmachine (0.12.10)
- eventmachine (0.12.10-java)
fakeweb (1.3.0)
ffi (0.6.3)
rake (>= 0.8.7)
@@ -91,7 +90,7 @@ GEM
term-ansicolor (1.0.5)
trollop (1.16.2)
weakling (0.0.4-java)
- webmock (1.3.3)
+ webmock (1.3.5)
addressable (>= 2.1.1)
crack (>= 0.1.7)
@@ -100,7 +99,7 @@ PLATFORMS
ruby
DEPENDENCIES
- bundler (~> 1.0.0.rc6)
+ bundler (~> 1.0.0)
capybara (~> 0.3.9)
cucumber (~> 0.8.5)
em-http-request (~> 0.2.7)
@@ -116,4 +115,4 @@ DEPENDENCIES
ruby-debug-base19 (= 0.11.23)
ruby-debug19
vcr!
- webmock (= 1.3.3)
+ webmock (~> 1.3.5)
2  README.md
View
@@ -47,7 +47,7 @@ maintenance) and accurate (the response from example.com will contain the same h
(all HTTP stubbing libraries), [Patron](http://github.com/toland/patron) (WebMock only),
[HTTPClient](http://github.com/nahi/httpclient) (WebMock only) and
[em-http-request](http://github.com/igrigorik/em-http-request) (WebMock only).
-* Request matching is configurable based on HTTP method, URI, host, body and headers.
+* Request matching is configurable based on HTTP method, URI, host, path, body and headers.
* The same request can receive different responses in different tests--just use different cassettes.
* The recorded requests and responses are stored on disk as YAML and can easily be inspected and edited.
* Dynamic responses are supported using ERB.
62 Rakefile
View
@@ -19,52 +19,46 @@ task :cleanup_rcov_files do
rm_rf 'coverage.data'
end
-begin
- permutations = {
- 'fakeweb' => %w( net/http ),
- 'webmock' => %w( net/http httpclient patron em-http-request )
- }
+permutations = {
+ 'fakeweb' => %w( net/http ),
+ 'webmock' => %w( net/http httpclient patron em-http-request )
+}
- require 'cucumber/rake/task'
- namespace :features do
- permutations.each do |http_stubbing_adapter, http_libraries|
- features_subtasks = []
+require 'cucumber/rake/task'
+namespace :features do
+ permutations.each do |http_stubbing_adapter, http_libraries|
+ features_subtasks = []
- namespace http_stubbing_adapter do
- http_libraries.each do |http_lib|
- next if RUBY_PLATFORM =~ /java/ && %w( patron em-http-request ).include?(http_lib)
+ namespace http_stubbing_adapter do
+ http_libraries.each do |http_lib|
+ next if RUBY_PLATFORM =~ /java/ && %w( patron em-http-request ).include?(http_lib)
- sanitized_http_lib = http_lib.gsub('/', '_')
- features_subtasks << "features:#{http_stubbing_adapter}:#{sanitized_http_lib}"
+ sanitized_http_lib = http_lib.gsub('/', '_')
+ features_subtasks << "features:#{http_stubbing_adapter}:#{sanitized_http_lib}"
- task "#{sanitized_http_lib}_prep" do
- ENV['HTTP_STUBBING_ADAPTER'] = http_stubbing_adapter
- ENV['HTTP_LIB'] = http_lib
- end
+ task "#{sanitized_http_lib}_prep" do
+ ENV['HTTP_STUBBING_ADAPTER'] = http_stubbing_adapter
+ ENV['HTTP_LIB'] = http_lib
+ end
- Cucumber::Rake::Task.new(
- { sanitized_http_lib => "#{features_subtasks.last}_prep" },
- "Run the features using #{http_stubbing_adapter} and #{http_lib}") do |t|
- t.cucumber_opts = ['--format', 'progress', '--tags', "@#{http_stubbing_adapter},@all_http_libs,@#{sanitized_http_lib}"]
+ Cucumber::Rake::Task.new(
+ { sanitized_http_lib => "#{features_subtasks.last}_prep" },
+ "Run the features using #{http_stubbing_adapter} and #{http_lib}") do |t|
+ t.cucumber_opts = ['--format', 'progress', '--tags', "@#{http_stubbing_adapter},@all_http_libs,@#{sanitized_http_lib}"]
- # disable scenarios on heroku that can't pass due to heroku's restrictions
- t.cucumber_opts += ['--tags', '~@spawns_localhost_server'] if ENV.keys.include?('HEROKU_SLUG')
- end
+ # disable scenarios on heroku that can't pass due to heroku's restrictions
+ t.cucumber_opts += ['--tags', '~@spawns_localhost_server'] if ENV.keys.include?('HEROKU_SLUG')
end
end
-
- desc "Run the features using #{http_stubbing_adapter} and each of #{http_stubbing_adapter}'s supported http libraries"
- task http_stubbing_adapter => features_subtasks
end
- end
- desc "Run the features using each supported permutation of http stubbing library and http library."
- task :features => permutations.keys.map { |a| "features:#{a}" }
-rescue LoadError
- task :features do
- abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
+ desc "Run the features using #{http_stubbing_adapter} and each of #{http_stubbing_adapter}'s supported http libraries"
+ task http_stubbing_adapter => features_subtasks
end
end
+desc "Run the features using each supported permutation of http stubbing library and http library."
+task :features => permutations.keys.map { |a| "features:#{a}" }
+
task :default => [:spec, :features]
4 features/step_definitions/net_http_steps.rb
View
@@ -19,8 +19,8 @@ def perform_net_http_get_with_returning_block(uri, path)
capture_response(url) do |uri, path|
result_body = ''
result = Net::HTTP.new(uri.host, uri.port).request_get(path) { |r| r.read_body { |fragment| result_body << fragment } }
- result.body.should == result_body
- result
+ def result_body.body; self; end # make the string a fake response (so response.body can be called on it)
+ result_body
end
end
4 features/step_definitions/vcr_steps.rb
View
@@ -210,7 +210,9 @@ def capture_response(url)
response_num = response_num.to_i || 0
response_num -= 1 if response_num > 0 # translate to 0-based array index.
regex = /#{regex_str}/i
- get_body_string(@http_requests[url][response_num]).should =~ regex
+ response = @http_requests[url][response_num]
+ raise response if response.is_a?(Exception)
+ get_body_string(response).should =~ regex
end
Then /^there should not be a "([^\"]*)" library file$/ do |cassette_name|
12 lib/vcr/extensions/net_http.rb
View
@@ -2,20 +2,24 @@
module Net
class HTTP
- alias_method :request_without_vcr, :request
-
- def request(request, body = nil, &block)
+ def request_with_vcr(request, body = nil, &block)
vcr_request = VCR::Request.from_net_http_request(self, request)
response = request_without_vcr(request, body)
match_attributes = (cass = VCR.current_cassette) ? cass.match_requests_on : VCR::RequestMatcher::DEFAULT_MATCH_ATTRIBUTES
if started? && !VCR.http_stubbing_adapter.request_stubbed?(vcr_request, match_attributes)
VCR.record_http_interaction VCR::HTTPInteraction.new(vcr_request, VCR::Response.from_net_http_response(response))
- response.extend VCR::Net::HTTPResponse # "unwind" the response
+
+ if VCR.http_stubbing_adapter.should_unwind_response?(response)
+ response.extend VCR::Net::HTTPResponse # "unwind" the response
+ end
end
yield response if block_given?
response
end
+
+ alias request_without_vcr request
+ alias request request_with_vcr
end
end
28 lib/vcr/extensions/net_http_response.rb
View
@@ -12,29 +12,19 @@
module VCR
module Net
module HTTPResponse
- def self.extended(object)
- object.instance_variable_set(:@__orig_body_for_vcr__, object.instance_variable_get(:@body))
- end
-
def read_body(dest = nil, &block)
- if @__orig_body_for_vcr__
- if dest && block
- raise ArgumentError.new("both arg and block given for HTTP method")
- elsif dest
- dest << @__orig_body_for_vcr__
- elsif block
- @body = ::Net::ReadAdapter.new(block)
- @body << @__orig_body_for_vcr__
- @body
- else
- @body = @__orig_body_for_vcr__
- end
- else
- super
+ return super if @__read_body_previously_called
+ return @body if dest.nil? && block.nil?
+ raise ArgumentError.new("both arg and block given for HTTP method") if dest && block
+
+ if @body
+ dest ||= ::Net::ReadAdapter.new(block)
+ dest << @body
+ @body = dest
end
ensure
# allow subsequent calls to #read_body to proceed as normal, without our hack...
- @__orig_body_for_vcr__ = nil
+ @__read_body_previously_called = true
end
end
end
4 lib/vcr/http_stubbing_adapters/fakeweb.rb
View
@@ -64,6 +64,10 @@ def ignore_localhost?
@ignore_localhost
end
+ def should_unwind_response?(response)
+ true
+ end
+
private
def version
6 lib/vcr/http_stubbing_adapters/webmock.rb
View
@@ -59,6 +59,12 @@ def ignore_localhost?
::WebMock::Config.instance.allow_localhost
end
+ def should_unwind_response?(response)
+ class << response
+ !ancestors.include?(::WebMock::Net::HTTPResponse)
+ end
+ end
+
private
def version
2  lib/vcr/version.rb
View
@@ -3,7 +3,7 @@ module VCR
def version
@version ||= begin
- string = [1, 1, 1].join('.')
+ string = '1.1.2'
def string.parts; VCR.version.split('.').map { |p| p.to_i }; end
def string.major; parts[0]; end
129 spec/extensions/net_http_response_spec.rb
View
@@ -1,85 +1,90 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-describe "Net::HTTP Response extensions" do
- context 'extending an already read response' do
- # WebMock uses this extension, too, and to prevent it from automatically being included
- # (and hence, causing issues with this spec), we remove all callbacks for the duration
- # of this spec, since the absence of any callbacks will prevent WebMock from reading the response
- # and extending the response with the response extension.
- before(:all) do
- @orig_webmock_callbacks = ::WebMock::CallbackRegistry.callbacks.dup
- ::WebMock::CallbackRegistry.reset
+describe VCR::Net::HTTPResponse do
+ # Disable the VCR/FakeWeb/WebMock Net::HTTP monkey patches so we don't have collisions with these specs
+ before(:all) { MonkeyPatches.disable! }
+ after(:all) { MonkeyPatches.enable! }
+
+ def self.it_allows_the_body_to_be_read(expected_regex)
+ it 'allows the body to be read using #body' do
+ response.body.to_s.should =~ expected_regex
end
- after(:all) do
- @orig_webmock_callbacks.each do |cb|
- ::WebMock::CallbackRegistry.add_callback(cb[:options], cb[:block])
- end
+ it 'allows the body to be read using #read_body' do
+ response.read_body.to_s.should =~ expected_regex
end
- def self.it_allows_the_body_to_be_read_again
- subject { @response.clone }
- let(:expected_regex) { /You have reached this web page by typing.*example\.com/ }
+ it 'allows the body to be read using #read_body with a block' do
+ yielded_body = ''
+ ret_val = response { |r| r.read_body { |s| yielded_body << s.to_s } }
+ yielded_body.should =~ expected_regex
+ end
- it 'allows the body to be read using #body' do
- subject.body.to_s.should =~ expected_regex
- end
+ it 'allows the body to be read by passing a destination string to #read_body' do
+ dest = ''
+ ret_val = response { |r| r.read_body(dest) }.body
+ dest.to_s.should =~ expected_regex
+ ret_val.to_s.should == dest
+ end
- it 'allows the body to be read using #read_body' do
- subject.read_body.to_s.should =~ expected_regex
- end
+ it 'raises an ArgumentError if both a destination string and a block is given to #read_body' do
+ dest = ''
+ expect { response { |r| r.read_body(dest) { |s| } } }.should raise_error(ArgumentError, 'both arg and block given for HTTP method')
+ end
- it 'allows the body to be read using #read_body with a block' do
- yielded_body = ''
- ret_val = subject.read_body { |s| yielded_body << s }
- yielded_body.should =~ expected_regex
- ret_val.should be_instance_of(Net::ReadAdapter)
- end
+ it 'raises an IOError when #read_body is called twice with a block' do
+ response { |r| r.read_body { |s| } }
+ expect { response { |r| r.read_body { |s| } } }.to raise_error(IOError, /read_body called twice/)
+ end
- it 'allows the body to be read by passing a destination string to #read_body' do
- dest = ''
- ret_val = subject.read_body(dest)
- dest.should =~ expected_regex
- ret_val.should == dest
- end
+ it 'raises an IOError when #read_body is called twice with a destination string' do
+ dest = ''
+ response { |r| r.read_body(dest) }
+ expect { response { |r| r.read_body(dest) } }.to raise_error(IOError, /read_body called twice/)
+ end
+ end
- it 'raises an ArgumentError if both a destination string and a block is given to #read_body' do
- dest = ''
- expect { subject.read_body(dest) { |s| } }.should raise_error(ArgumentError, 'both arg and block given for HTTP method')
- end
+ { :get => /You have reached this web page by typing.*example\.com/, :head => /\A\z/ }.each do |http_verb, expected_body_regex|
+ context "for a #{http_verb.to_s.upcase} request" do
+ let(:http_verb_method) { :"request_#{http_verb}" }
- it 'raises an IOError when #read_body is called twice with a block' do
- subject.read_body { |s| }
- expect { subject.read_body { |s| } }.to raise_error(IOError, /read_body called twice/)
- end
+ def response(&block)
+ if @response && block
+ block.call(@response)
+ return @response
+ end
- it 'raises an IOError when #read_body is called twice with a destination string' do
- dest = ''
- subject.read_body(dest)
- expect { subject.read_body(dest) }.to raise_error(IOError, /read_body called twice/)
+ @response ||= begin
+ http = Net::HTTP.new('example.com', 80)
+ res = http.send(http_verb_method, '/', &block)
+ res.should_not be_a(VCR::Net::HTTPResponse)
+ res.should_not be_a(::WebMock::Net::HTTPResponse)
+ res
+ end
end
- end
- context 'when the body has already been read using #read_body and a dest string' do
- before(:each) do
- http = Net::HTTP.new('example.com', 80)
- dest = ''
- @response = http.request_get('/') { |res| res.read_body(dest) }
- @response.extend VCR::Net::HTTPResponse
+ context 'when the body has not already been read' do
+ it_allows_the_body_to_be_read(expected_body_regex)
end
- it_allows_the_body_to_be_read_again
- end
+ context 'when the body has already been read using #read_body and a dest string' do
+ before(:each) do
+ dest = ''
+ response { |res| res.read_body(dest) }
+ response.extend VCR::Net::HTTPResponse
+ end
- context 'when the body has already been read using #body' do
- before(:each) do
- http = Net::HTTP.new('example.com', 80)
- @response = http.request_get('/')
- @response.body
- @response.extend VCR::Net::HTTPResponse
+ it_allows_the_body_to_be_read(expected_body_regex)
end
- it_allows_the_body_to_be_read_again
+ context 'when the body has already been read using #body' do
+ before(:each) do
+ response.body
+ response.extend VCR::Net::HTTPResponse
+ end
+
+ it_allows_the_body_to_be_read(expected_body_regex)
+ end
end
end
end
8 spec/http_stubbing_adapters/fakeweb_spec.rb
View
@@ -3,6 +3,14 @@
describe VCR::HttpStubbingAdapters::FakeWeb do
it_should_behave_like 'an http stubbing adapter', ['net/http'], [:method, :uri, :host, :path]
+ describe '#should_unwind_response?' do
+ let(:response) { ::Net::HTTPOK.new('1.1', 200, 'OK') }
+
+ it 'returns true' do
+ described_class.should_unwind_response?(response).should be_true
+ end
+ end
+
describe '#check_version!' do
disable_warnings
before(:each) { @orig_version = FakeWeb::VERSION }
13 spec/http_stubbing_adapters/webmock_spec.rb
View
@@ -5,6 +5,19 @@
%w[net/http patron httpclient em-http-request],
[:method, :uri, :host, :path, :body, :headers]
+ describe '#should_unwind_response?' do
+ let(:response) { ::Net::HTTPOK.new('1.1', 200, 'OK') }
+
+ it 'returns true when the response has not been extended with WebMock::Net::HTTPResponse' do
+ described_class.should_unwind_response?(response).should be_true
+ end
+
+ it 'returns false when the response has been extended with WebMock::Net::HTTPResponse' do
+ response.extend WebMock::Net::HTTPResponse
+ described_class.should_unwind_response?(response).should be_false
+ end
+ end
+
describe '#check_version!' do
before(:each) { WebMock.should respond_to(:version) }
70 spec/monkey_patches.rb
View
@@ -0,0 +1,70 @@
+module MonkeyPatches
+ extend self
+
+ NET_HTTP_SINGLETON = class << Net::HTTP; self; end
+
+ MONKEY_PATCHES = [
+ [Net::BufferedIO, :initialize],
+ [Net::HTTP, :request],
+ [Net::HTTP, :connect],
+ [NET_HTTP_SINGLETON, :socket_type]
+ ]
+
+ def enable!
+ MONKEY_PATCHES.each do |mp|
+ realias mp.first, mp.last, :with_monkeypatches
+ end
+ end
+
+ def disable!
+ MONKEY_PATCHES.each do |mp|
+ realias mp.first, mp.last, :without_monkeypatches
+ end
+ end
+
+ def init
+ # capture the monkey patched definitions so we can realias to them in the future
+ MONKEY_PATCHES.each do |mp|
+ capture_method_definition(mp.first, mp.last, false)
+ end
+ end
+
+ private
+
+ def capture_method_definition(klass, method, original)
+ klass.class_eval do
+ monkeypatch_methods = [
+ :with_vcr, :without_vcr,
+ :with_fakeweb, :without_fakeweb,
+ :with_webmock, :without_webmock
+ ].select do |m|
+ method_defined?(:"#{method}_#{m}")
+ end
+
+ if original
+ if monkeypatch_methods.size > 0
+ raise "The following monkeypatch methods have already been defined #{method}: #{monkey_patch_methods.inspect}"
+ end
+ alias_name = :"#{method}_without_monkeypatches"
+ else
+ if monkeypatch_methods.size == 0
+ raise "No monkey patch methods have been defined for #{method}"
+ end
+ alias_name = :"#{method}_with_monkeypatches"
+ end
+
+ alias_method alias_name, method
+ end
+ end
+
+ # capture the original method definitions before the monkey patches have been defined
+ # so we can realias to the originals in the future
+ MONKEY_PATCHES.each do |mp|
+ capture_method_definition(mp.first, mp.last, true)
+ end
+
+ def realias(klass, method, alias_extension)
+ klass.class_eval { alias_method method, :"#{method}_#{alias_extension}" }
+ end
+end
+
5 spec/spec_helper.rb
View
@@ -2,6 +2,8 @@
require 'bundler'
Bundler.setup
+require 'monkey_patches'
+
require 'patron' unless RUBY_PLATFORM =~ /java/
require 'httpclient'
require 'em-http-request' unless RUBY_PLATFORM =~ /java/
@@ -38,3 +40,6 @@
config.filter_run :focus => true
config.run_all_when_everything_filtered = true
end
+
+MonkeyPatches.init
+
4 vcr.gemspec
View
@@ -17,14 +17,14 @@ Gem::Specification.new do |s|
s.required_rubygems_version = '>= 1.3.5'
{
- 'bundler' => '~> 1.0.0.rc6',
+ 'bundler' => '~> 1.0.0',
'rake' => '~> 0.8.7',
'rspec' => '~> 2.0.0.beta.20',
'cucumber' => '~> 0.8.5',
'fakeweb' => '~> 1.3.0',
- 'webmock' => '1.3.3',
+ 'webmock' => '~> 1.3.5',
'httpclient' => '~> 2.1.5.2',

No commit comments for this range

Something went wrong with that request. Please try again.