Search code examples
rubyapirspecvcrwebmock

How would I use VCR (with WebMock) in this scenario?


I'm developing a DSL for building API wrappers, named Hendrix. I am having problems with the testing of the DSL. As it is a API wrapper, it needs to interact with external services. I am not sure how to approach this in terms of testing. I'm using RSpec and tried configuring VCR with WebMock, but no luck. How am I supposed to test this particular scenario if I don't have direct access to what request is being made?

This is my spec_helper.rb:

$VERBOSE = nil

require 'simplecov'
require 'coveralls'

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
  SimpleCov::Formatter::HTMLFormatter,
  Coveralls::SimpleCov::Formatter
]
SimpleCov.start { add_filter '/spec/' }

lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'hendrix'

require 'vcr'

VCR.configure do |c|
  c.cassette_library_dir = 'spec/cassettes'
  c.hook_into :webmock
end

RSpec.configure do |config|
  config.treat_symbols_as_metadata_keys_with_true_values = true
  config.run_all_when_everything_filtered = true
  config.filter_run :focus
  config.order = 'random'
  config.extend VCR::RSpec::Macros
end

The project is in its early stages (working towards version 0.1.0 at the moment). The syntax of the DSL is as follows:

require 'hendrix'

Hendrix.build 'Jimi' do
  base 'https://api.github.com'

  client :issues do
    action :issue, '/repos/:owner/:repo/issues/:number'
  end
end

Jimi.issue('rafalchmiel', 'hendrix', 1)
  # => {"url"=>"https://api.github.com/repos/rafalchmiel/hendrix/issues/1",
  #  "labels_url"=> ...
Jimi.issue('rafalchmiel', 'hendrix', 1).title
  # => "Implement parameters in actions"

In most specs, I'm testing what the methods from the master module (in this case Jimi.issue etc) return and whether it is in a Hashie::Mash format. How would I test this? I don't know where to start.


Solution

  • For integration tests, I usually stub the endpoint with webmock directly, without trying to record an actual request. This means you can control the response and the expectation in the same place. You can place expectations on whether your library parses the response correctly and you can write tests that verify that the request has been made correctly. Go through each of the features of your gem to get a list of features. Here's an example:

    require "webmock/rspec"
    
    describe "parsing results" do
    
      let(:url) { "http://..." }
    
      it "parses results into nice methods" do
        stub_request(:get, url)
          .to_return(
            body: { title: "implement" }.to_json,
            headers: { content_type: "application/json" },
          )
    
        perform_request
        expect(response.title).to eq "implement"
      end
    
      it "sends the user agent header correctly" do
        stub_request(:get, url)
        perform_request
        expect(a_request(:get, url).with(
          headers: { user_agent: "hendrix" }
        )).to have_been_made.once
      end
    
      it "interpolates values in URLs"
    
      it "supports DELETE requests"
    
      it "supports HTTP Basic"
    
      def perform_request
        # ...
      end
    
    end
    

    Try not to record real requests: it's hard to control the right circumstances with real web servers, especially if you're not the one who wrote the specs. Especially when you write a general purpose library like this. VCR is nice if you want to access one particular server and your code really depends on that one server.

    Also don't check on types. I see that quite a lot in your gem right now. Nobody cares if you return a Hashie::Mash object. As my first spec shows, you just want to be able to access the attributes cleanly.