The Scenario
You’ve just finished integrating the Stripe API (or OpenAI, or Twilio) into your Rails app. It works perfectly in development. You push it, run your test suite, and... wait.
And wait.
And then BOOM. Your tests fail because:
- The external API is down.
- You hit a rate limit because your CI ran 50 tests in 2 seconds.
- Your laptop isn't connected to WiFi.
Stop making real HTTP requests in your test suite.
It’s slow, it’s flaky, and it’s dangerous. Today, we’re going to solve this using the dynamic duo of the Ruby world: WebMock and VCR.
The Tools
- WebMock: The bouncer. It intercepts every single HTTP request your application tries to make and says, "No."
- VCR: The recorder. It records the HTTP interaction the first time it happens and saves it to a YAML "cassette." On future runs, it replays that YAML file instantly.
Step 1: The Setup
Add these to your Gemfile in the test group:
group :test do
gem 'rspec-rails' # Assuming you use RSpec, but works with Minitest too!
gem 'webmock'
gem 'vcr'
end
Run bundle install.
Next, configure VCR in your spec/rails_helper.rb (or test/test_helper.rb).
# spec/rails_helper.rb
require 'vcr'
require 'webmock/rspec'
VCR.configure do |config|
# Where to save the 'cassettes' (the recorded interactions)
config.cassette_library_dir = "spec/vcr_cassettes"
# Hook into WebMock
config.hook_into :webmock
# Allow localhost so we don't block feature tests (Capybara/Selenium)
config.ignore_localhost = true
# If VCR throws an error, print the metadata
config.configure_rspec_metadata!
end
Step 2: Writing the Test
Let's pretend we have a service that fetches a user's GitHub profile.
# app/services/github_service.rb
class GithubService
def self.user_info(username)
response = Faraday.get("https://api.github.com/users/#{username}")
JSON.parse(response.body)
end
end
Now, let's test it. We use the vcr: true metadata tag (if you configured configure_rspec_metadata!) or wrap the block manually.
# spec/services/github_service_spec.rb
require 'rails_helper'
RSpec.describe GithubService do
# We name the cassette specifically here
it 'fetches the user profile', vcr: { cassette_name: 'github/user_info' } do
result = GithubService.user_info('dhh')
expect(result['login']).to eq('dhh')
expect(result['id']).to be_a(Integer)
end
end
Step 3: The Magic (First Run vs. Second Run)
Run 1:
When you run rspec the first time, VCR sees that you don't have a cassette recorded yet. It allows the real HTTP request to go through to GitHub, captures the response, and saves it to spec/vcr_cassettes/github/user_info.yml.
Run 2:
You run rspec again. VCR intercepts the request. It does not hit GitHub. It reads the YAML file.
- Speed: < 0.01 seconds.
- Stability: 100%.
⚠️ Crucial: Don't Leak Your API Keys!
If you are testing an API that requires a Secret Key (like OpenAI or Stripe), VCR will record your real API key into the YAML file. If you commit that file to GitHub, bots will scrape it, and you will have a bad day.
Fix this by filtering sensitive data in your config:
# spec/rails_helper.rb
VCR.configure do |config|
# ... previous config ...
# Replace your real ENV var with a placeholder string in the cassette
config.filter_sensitive_data('<GITHUB_TOKEN>') { ENV['GITHUB_TOKEN'] }
config.filter_sensitive_data('<STRIPE_SECRET>') { ENV['STRIPE_SECRET_KEY'] }
end
Now, if you open your generated YAML cassette, you will see Authorization: Bearer <GITHUB_TOKEN> instead of your actual key. Safe to commit!
When to "Re-Record"
External APIs change. If GitHub changes their JSON structure, your test will still pass (because it's reading the old cassette), but your production app will crash.
Every once in a while, or when you modify the API logic, delete the cassette file:
rm spec/vcr_cassettes/github/user_info.yml
Run the test again to generate a fresh recording.
Summary
- WebMock cuts the internet connection.
- VCR records the request once and replays it forever.
- Filter Sensitive Data so you don't leak keys.
Your test suite is now deterministic, lightning-fast, and rate-limit proof. Happy coding!
Did this save your CI pipeline? Let me know in the comments!
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.