Skip to content

Instantly share code, notes, and snippets.

@mattbrictson
Created September 11, 2015 15:55
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattbrictson/72910465f36be8319cde to your computer and use it in GitHub Desktop.
Save mattbrictson/72910465f36be8319cde to your computer and use it in GitHub Desktop.
Using vcr with Minitest + Rails (test unit style)
group :test do
gem "minitest"
gem "vcr"
gem "webmock", "~> 1.15.0"
end
# test/test_helper.rb
# Load everything from test/support
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each { |rb| require(rb) }
# test/support/vcr.rb
require "vcr"
VCR.configure do |config|
config.allow_http_connections_when_no_cassette = false
config.cassette_library_dir = File.expand_path("../../cassettes", __FILE__)
config.hook_into :webmock
config.ignore_request { ENV["DISABLE_VCR"] }
config.ignore_localhost = true
config.default_cassette_options = {
:record => :new_episodes
}
end
# Monkey patch the `test` DSL to enable VCR and configure a cassette named
# based on the test method. This means that a test written like this:
#
# class OrderTest < ActiveSupport::TestCase
# test "user can place order" do
# ...
# end
# end
#
# will automatically use VCR to intercept and record/play back any external
# HTTP requests using `cassettes/order_test/_user_can_place_order.yml`.
#
class ActiveSupport::TestCase
def self.test(test_name, &block)
return super if block.nil?
cassette = [name, test_name].map do |str|
str.underscore.gsub(/[^A-Z]+/i, "_")
end.join("/")
super(test_name) do
VCR.use_cassette(cassette) do
instance_eval(&block)
end
end
end
end
@fredplante
Copy link

@mattbrictson Thanks, it works well. However, it breaks the possibility to run a single test giving a line number. Also, when a test fails, rails output the line from the monkey patch: rails test test/support/vcr.rb:36 instead of the testcase. Any idea how to solve this?

@mattbrictson
Copy link
Author

Since I wrote this gist 6 years ago I have moved to a different technique. I now use a module that defines a with_expiring_vcr_cassette method and explicitly include that module in my test classes where I want vcr. Then I'll wrap the code within an test with with_expiring_vcr_cassette.

I'm not sure that solves the problems you mentioned with the monkey patch, but it has been serving me well.

My preference nowadays is exclude cassettes from git and expire them periodically so they get regenerated. That way most tests are fast (using the cassettes as sort of cache), but they will regularly get refreshed so I can be sure that the actual end-to-end HTTP calls continue to get exercised. This helps guard against an API change in the external system that I would not notice if the cassettes were permanently recorded.

module WithVCR
  private

  def with_expiring_vcr_cassette
    names = self.class.name.split("::")
    cassette_path = names.map { |s| s.gsub(/[^A-Z0-9]+/i, "_") }.join("/")

    VCR.use_cassette(cassette_path) do |cassette|
      if File.exist?(cassette.file)
        age = Time.current - File.mtime(cassette.file)
        FileUtils.rm(cassette.file) if age > 60 * 60 * 24
      end
      begin
        yield(cassette)
      rescue StandardError
        FileUtils.rm(cassette.file) if File.exist?(cassette.file)
        raise
      end
    end
  end
end

@fredplante
Copy link

Thanks Matt! I use the same approach now and it works perfectly well 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment