-
-
Save mattbrictson/72910465f36be8319cde to your computer and use it in GitHub Desktop.
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 |
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
Thanks Matt! I use the same approach now and it works perfectly well 👍
@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?