3

I'm trying to stub any external API calls in my test suite, but the before(:suite) is never executed. Webmock always reports that I need to stub the maps.googleapis.com even though no tests have been run yet (no green dots, no red Fs).

spec_helper.rb:

require 'webmock/rspec'
WebMock.disable_net_connect!(allow_localhost: true)

...

config.before(:suite) do
    puts "THIS NEVER SHOWS"
    stub_request(:get, "maps.googleapis.com").
      with(headers: {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
      to_return(status: 200, body: "", headers: {})
end

The geocoder gem ends up trying to save the lat/lon from googleapis.com and an error is raised by Webmock saying that the URL is unregistered.

EDIT: Error snippet:

$ bundle exec rspec spec/factories_spec.rb
/home/jake/.rvm/gems/ruby-2.1.0@global/gems/webmock-1.17.4/lib/webmock/http_lib_adapters/net_http.rb:114:in `request': Real HTTP connections are disabled. Unregistered request: GET http://maps.googleapis.com/maps/api/geocode/json?address=[private]&language=en&sensor=false with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'} (WebMock::NetConnectNotAllowedError)

You can stub this request with the following snippet:

stub_request(:get, "http://maps.googleapis.com/maps/api/geocode/json?address=[private]&language=en&sensor=false").
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
  to_return(:status => 200, :body => "", :headers => {})

============================================================
    from /home/jake/.rvm/gems/ruby-2.1.0@global/gems/geocoder-1.1.9...

    ...

Again, I'll stress that this has to do with the fact that the code in the config.before(:each) block is never run. Why? Because if it was, I could "raise 'WTF'" and 'WTF' should appear in the console output instead of the error you see above. I only see 'WTF' when I "un-bundle" the Webmock gem.

wurde
  • 2,487
  • 2
  • 20
  • 39
  • Just to double check, your `config.before(:suite) do` block is within `RSpec.configure do |config|`, right? I imagine you could also try isolating one test and putting all of the Webmock related code at the beginning of it to make sure you haven't done anything strange in your rspec config. – mralexlau Mar 23 '14 at 00:43
  • That's correct. I put the "puts 'THIS NEVER SHOWS'" in there just to see if it was being run and it never shows. I get the Webmock error before any tests are run. – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 00:46
  • Scratch that last part...I have a factories_spec.rb which does a create() on all factories. The error occurs in there. Either way the before(:suite) isn't being called. – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 00:50
  • I would probably try to put it in a `before :each` block instead, as demonstrated in [this thoughtbot article](http://robots.thoughtbot.com/how-to-stub-external-services-in-tests). Hard to say why the `config.before(:suite)` block isn't running without seeing all of your code... especially since it looks fine syntactically. – mralexlau Mar 23 '14 at 01:11
  • I did notice in the [rspec documentation](https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/hooks/before-and-after-hooks) that "Setting instance variables are not supported in before(:suite)." So that may have something to do with it as well depending on how `stub_request` is implemented. – mralexlau Mar 23 '14 at 01:12
  • I actually initially tried it in the before(:each) but it didn't work - I decided to put it in before(:suite) just for the sake of optimization. Weirdly enough, I commented out WebMock.disable_net_connect!(allow_localhost: true) and it's still throwing the error. – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 01:36
  • To isolate the problem, I would try commenting out all but one test, then putting both the call to `disable_net_connect` *and* `stub_request` in the single test you have. That will at least tell you if the issue is with how you've configured rspec, or if you're somehow misusing webmock. – mralexlau Mar 23 '14 at 01:41
  • Simply the presence of the gem in bundler means that the error will be raised. I commented out all code using Webmock in my tests and rspec_helper.rb and that didn't change anything. I then commented out the gem in my Gemfile, ran 'bundle', then ran my tests again. The text "THIS NEVER SHOWS" now shows. I then uncommented, re-ran bundle and then rspec and the error popped up again. It's like Webmock analyzes the spec files before rspec even has a chance to get at them or something. FYI I'm running "bundle exec guard" or "bundle exec rspec rspecfile". – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 01:48
  • Dumb question - have you tried `http://maps.googleapis.com` instead of `maps.googleapis.com` in your call to `stub_request`? – mralexlau Mar 23 '14 at 01:55
  • I didn't before because no other examples showed having to use the protocol. However I did just try it yielding the same results. This doesn't surprise me because I can't even raise an error from within the block. The before(:each) or before(:suite) block is never run. – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 02:03
  • Could you edit your question to include a snippet of the actual error? Another thing to try would be passing a regex to stub_request, e.g. `stub_request(:get, /maps.googleapis.com/)` – mralexlau Mar 23 '14 at 02:10
  • Updated. Sorry it took a while - I'm switching between Ubuntu VM and the copy-paste doesn't work between the host and VM. – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 02:19
  • I got nothing :/ The only other thing I would try is the suggestion from before of putting all of your Webmock-related code in one single test. You mentioned "I commented out all code using Webmock in my tests and rspec_helper.rb and that didn't change anything." - which actually isn't too surprising since it appears your request isn't being stubbed in the first place. If you can get it to work in a single test then that at least proves that it *is* the `before` block that's failing to execute. – mralexlau Mar 23 '14 at 02:37
  • I actually can get it to work in one of my other tests, simply because I had put a stub_request call right in the test itself. I only noticed this error when I ran ALL my tests (ones that didn't have any calls to stub_request). – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 02:50
  • I posted it as an issue in the Webmock github page. – SnakeWasTheNameTheyGaveMe Mar 23 '14 at 02:51

1 Answers1

1

Well I was doing "something cute" with my RSpec tests by creating tests at runtime depending on whether or not the Factory has an attribute that is a file. Due to the way my factories/models were set up, factories were being created (saved) when the attributes for a certain factory were being read, so the block of code that's generating the tests runs outside of RSpec's config.before(:suite) and WebMock raises the error.

https://github.com/bblimke/webmock/issues/378

Moreover, here's specifically what I was doing wrong - not related to WebMock:

1) In my factories.rb, I was calling create() for associations which may not yet exist. Why? Because RSpec was giving me errors saying "[association] was blank". It was doing that because I had validates_presence_of :association_id instead of just :association. When I used create() instead of build(), it "worked". Of course when it came time to use WebMock, I was creating (and thus saving) objects calling geocoder to do it's thing. The solution was to fix validates_presence_of to use the right attribute and use build() instead of create() in my factories.

Bad Example:

# In spec/factories.rb
factory :review, class: Manager::Review do
    rating 4
    wine { Manager::Wine.first || create(:wine) }
    reviewer { Manager::Reviewer.first || create(:reviewer) }
    date Time.now
    association :referral, referrable_id: 1, referrable_type: Manager::Review, strategy: :build
end

# In app/models/manager/review.rb
validates_presence_of :rating_id, :wine_id, :reviewer_id, :date

Good Example:

# In spec/factories.rb
factory :review, class: Manager::Review do
    rating 4
    wine { Manager::Wine.first || build(:wine) }
    reviewer { Manager::Reviewer.first || build(:reviewer) }
    date Time.now
    association :referral, referrable_id: 1, referrable_type: Manager::Review, strategy: :build
end

# In app/models/manager/review.rb
validates_presence_of :rating, :wine, :reviewer, :date

2) FWIW, I told geocoder to fetch the geocode before_save, not after_validate like it suggests in their home page.

Also, you cannot stub with WebMock in the before(:suite), but it works in before(:each)