vcr / vcr

Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.
https://benoittgt.github.io/vcr
Other
5.78k stars 500 forks source link

2.0.0rc2 custom WebMock.stub_http_request #139

Closed masterkain closed 12 years ago

masterkain commented 12 years ago

Hello, in my rspec config.before(:suite) I was using this:

# Avoid Push messages going everywhere.
WebMock.stub_http_request(:any, /my.test.host/)

Since those http requests are rather frequent in my application I didn't want to put cassettes up every single test, nor I want the external server be up, so I was manually telling WebMock to stub the request going to this particular host.

However when upgrading to 2.0.0rc2 from 1.11.3 this does not work anymore as it's trapped by VCR that raises the classic VCR::Errors::UnhandledHTTPRequestError.

I don't really want to ignore those requests, just stub them out, what are my options?

myronmarston commented 12 years ago

With VCR 2, you need to turn VCR off if you want to use the underlying FakeWeb/WebMock/whatever APIs, as you're trying to do here. You can ensure VCR is always turned off for these requests using an around_http_request hook:

# Avoid Push messages going everywhere.
WebMock.stub_http_request(:any, /my.test.host/)

VCR.configure do |c|
  c.around_http_request(lambda {|r| r.uri =~ /my.test.host/}) do |request|
    VCR.turned_off(&request)
  end
end

Let me know if that works for you.

BTW, thanks for installing and trying 2.0.0.rc2. I just released it a couple hours ago :).

myronmarston commented 12 years ago

One other thought...if the primary reason you don't to use VCR cassettes for these requests is because of needing to use VCR.use_cassette in tons of tests for this, you can use the new around http_request hook to globally use a cassette for them:

VCR.configure do |c|
  c.around_http_request(lambda {|r| r.uri =~ /my.test.host/}) do |request|
    VCR.use_cassette("my_test_host", &request)
  end
end
masterkain commented 12 years ago

This is so awesome, around_http_request is working well; will continue testing, thanks!

myronmarston commented 12 years ago

This is so awesome, around_http_request is working well; will continue testing, thanks!

Glad to hear it :). I plan to release VCR 2.0.0 final in the next week or so, so please report any issues you find before then!

masterkain commented 12 years ago

Apparently I'm having some issues using RSpec's shared examples along with VCR:

     Failure/Error: Unable to find matching line from backtrace
     VCR::Errors::CassetteInUseError:
       A VCR cassette is currently in use.  You must eject it before you can turn VCR off.
     Shared Example Group: "a media file" called from ./spec/models/audio_file_spec.rb:17
     # ./spec/support/vcr.rb:18:in `block (2 levels) in <top (required)>'

In my shared example I make use of use_vcr_cassette in some describe blocks; the shared example is pulled in the main spec file with it_behaves_like "a media file"

Edit: do you want me to open another ticket for this?

WebMock.stub_http_request(:any, /push.my.test/)

vcr.rb

VCR.configure do |c|
  c.hook_into :webmock
  c.around_http_request(lambda {|r| r.uri =~ /push.my.test/}) do |request|
    VCR.turned_off(&request)
  end # line 18

Perhaps this is happening because I send push messages (mocked by turning off VCR like above) when an object gets created, then I test its behavior in the shared example group along with a VCR cassette.

myronmarston commented 12 years ago

Hmm...that behavior you're seeing is by design, actually. The semantics of turning VCR off while there's a cassette in use get weird and confusing real quick; so when you turn it off, VCR raises an error if there's currently a cassette in use.

Instead, will this work for you?

# Avoid Push messages going everywhere.
WebMock.stub_http_request(:any, /my.test.host/)

VCR.configure do |c|
  c.ignore_request { |r| r.uri =~ /my.test.host/ }
end

I think that the WebMock stub you have set should still work when VCR is ignoring the request. There are some parts of the WebMock API that you won't be able to use while VCR is turned on (since VCR uses certain WebMock APIs to do its thing, and it would get confusing if it allowed you to accidentally screw with VCR by using WebMock), but I believe setting a basic stub like that should work fine.

masterkain commented 12 years ago

Thanks Myron, that worked, apparently I have a last issue.

In one of my spec I call http methods two times, resulting in a cassette containing two requests, the problem is that this cassette in 2.0.0rc2 is not able to play back the content although both method and uri matches perfectly.

Sample code (this is always happening in a shared example):

describe "#report_lyrics_mistake!" do
  use_vcr_cassette('musixmatch/report_mistake', record: :once)
  it "should return false if status code is not 200" do
    entity = subject.musixmatch_entity # first GET http call to fetch info, fails
    MusixMatch.should_receive(:post_feedback).with(entity.track_id, entity.lyrics_id, 'bad_characters').and_return(@mock = mock('test'))
    @mock.stub(:status_code).and_return(401)
    subject.report_lyrics_mistake!('bad_characters').should be_false # second GET http call
  end
end

       An HTTP request has been made that VCR does not know how to handle:
         GET http://api.musixmatch.com/ws/1.1/track.search?apikey=<edited>&format=json&q=Michael%20Jackson%20-%20Just%20Good%20Friends

       VCR is currently using the following cassette:
         - /Users/kain/Sites/myapp/rails/spec/fixtures/musixmatch/report_mistake.yml
         - :record => :once
         - :match_requests_on => [:method, :uri]
masterkain commented 12 years ago

VCR gives me some hints though:

         * The cassette contains an HTTP interaction that matches this request,
           but it has already been played back. If you wish to allow a single HTTP
           interaction to be played back multiple times, set the `:allow_playback_repeats`
           cassette option [4].
         * The cassette contains 1 HTTP interaction that has not been
           played back. If your request is non-deterministic, you may need to
           change your :match_requests_on cassette option to be more lenient
           or use a custom request matcher to allow it to match [5].
myronmarston commented 12 years ago

Can you try the new debug_logger option? It was designed to help in situations like these. Paste the output when you have it and I can take a look--or maybe you'll be able to interpret it yourself :).

One possibility...is this a cassette that was recorded with 2.0.0.rc2? Or did you record it on 1.x and upgrade it? There may be some weird edge cases that don't upgrade perfectly, so you might try deleting and re-recording the cassette.

masterkain commented 12 years ago

Yeah the cassette is new, I deleted the old ones and re-run my specs in order to recreate them. I'm going to try debug_logger, will post back.

masterkain commented 12 years ago

Weird, I used

c.debug_logger = File.open(Rails.root.join('log', 'vcr.log'), 'w')

it created the vcr.log file but didn't wrote to it.

masterkain commented 12 years ago

Ok, it started writing after I put the rspec focus tag on the main describe block just before the use_vcr_cassette is defined instead of the direct example.

I ended up mocking the first request so the specs passes, but I think it's something I should look at because it was working well with the previous stable version; I'll open another ticket about the matter once I'm able to gather more info.

Cheers

myronmarston commented 12 years ago

@masterkain -- I was going to wait until you opened another issue for this, but I have some thoughts that may help so I figured I'd just respond here :).

In VCR 1.x, VCR relied upon the HTTP stubbing library (such as WebMock) to do the URI matching. WebMock does a bunch of normalization internally, so that it considers a URL like http://foo.com/bar?a=1&b=2 to be equal to http://foo.com/bar?b=2&a=1, even though the strings are not equal:

1.9.2p290 :002 > require 'webmock'
 => true 
1.9.2p290 :003 > uri1 = "http://foo.com/bar?a=1&b=2"
 => "http://foo.com/bar?a=1&b=2" 
1.9.2p290 :004 > uri2 = "http://foo.com/bar?b=2&a=1"
 => "http://foo.com/bar?b=2&a=1" 
1.9.2p290 :005 > uri1 == uri2
 => false 
1.9.2p290 :006 > WebMock::Util::URI.normalize_uri(uri1) == WebMock::Util::URI.normalize_uri(uri2)
 => true 

In VCR 2.x, the internals were rewritten so that it simply hooks into every HTTP request, and calls each request matcher to determine if the requests match. The request matchers themselves are now very simple one liners. The :uri matcher just uses String#== to match.

So...if I had to guess, the URL is slightly different on the second test run compared to the first. Maybe the query params are ordered differently? The debug_logger option should give you some insight into what's going on. You can also redefine the :uri matcher:

VCR.configure do |c|
  c.register_request_matcher(:uri) do |r1, r2|
    WebMock::Util::URI.normalize_uri(r1.uri) == WebMock::Util::URI.normalize_uri(r2.uri)
  end
end