Testing External Services with RSpec, VCR, and WebMock in Ruby on Rails

David Paluy - Nov 2 - - Dev Community

Testing External Services with RSpec, VCR, and WebMock in Ruby on Rails

For quick setup, just run:

rails app:template LOCATION='https://railsbytes.com/script/X8Bsyo'
Enter fullscreen mode Exit fullscreen mode

👉 View template source


Introduction

When building Rails applications that integrate with external services, it's crucial to have reliable tests that don't depend on actual HTTP requests. This tutorial will show you how to use VCR and WebMock to record and replay HTTP interactions in your RSpec tests.

Setup

First, add these gems to your Gemfile:

group :test do
  gem 'rspec-rails'
  gem 'vcr'
  gem 'webmock'
end
Enter fullscreen mode Exit fullscreen mode

Run bundle install to install the gems.

Configuring VCR and WebMock

Create a new file at spec/support/vcr.rb:

require 'vcr'
require 'webmock/rspec'

# Disable all real HTTP connections, except to localhost
WebMock.disable_net_connect!(allow_localhost: true)

VCR.configure do |c|
  # Where VCR will store the recorded HTTP interactions
  c.cassette_library_dir = Rails.root.join('spec', 'cassettes')

  # Ignore localhost requests
  c.ignore_localhost = true

  # Tell VCR to use WebMock
  c.hook_into :webmock

  # Allow VCR to be configured through RSpec metadata
  c.configure_rspec_metadata!

  # Allow real HTTP connections when no cassette is in use
  c.allow_http_connections_when_no_cassette = true

  # Configure how requests are matched
  c.default_cassette_options = { 
    match_requests_on: [:method, :uri, :body] 
  }

  # Filter sensitive data before recording
  c.before_record do |interaction|
    interaction.request.headers['Authorization'] = '[FILTERED]'
  end

  # Ignore specific hosts
  ignored_hosts = ['codeclimate.com']
  c.ignore_hosts *ignored_hosts
end

# Configure RSpec to use VCR
RSpec.configure do |config|
  config.around(:each) do |example|
    options = example.metadata[:vcr] || {}
    options[:allow_playback_repeats] = true

    if options[:record] == :skip
      VCR.turned_off(&example)
    else
      custom_name = example.metadata[:vcr]&.delete(:cassette_name)
      generated_name = example.metadata[:full_description]
        .split(/\s+/, 2)
        .join('/')
        .underscore
        .tr('.', '/')
        .gsub(/[^\w\/]+/, '_')
        .gsub(/\/$/, '')

      name = custom_name || generated_name
      VCR.use_cassette(name, options, &example)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Using VCR in Your Tests

Here are some examples of how to use VCR in your specs:

Basic Usage

# spec/services/weather_service_spec.rb
RSpec.describe WeatherService do
  describe '#get_forecast' do
    it 'fetches weather data from the API' do
      service = WeatherService.new
      forecast = service.get_forecast('London')

      expect(forecast).to include('temperature')
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Custom Cassette Names

RSpec.describe WeatherService do
  describe '#get_forecast' do
    it 'fetches weather data', vcr: { cassette_name: 'weather_api/london_forecast' } do
      # Your test code
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

It will make a first connection to the external service and record the response into a cassete

Skipping VCR

it 'makes real HTTP requests', vcr: { record: :skip } do
  # VCR will be disabled for this test
end
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Sensitive Data: Always filter sensitive information like API keys and tokens:
VCR.configure do |c|
  c.filter_sensitive_data('<API_KEY>') { ENV['API_KEY'] }
end
Enter fullscreen mode Exit fullscreen mode
  1. Match Request Bodies: When testing POST requests, make sure to match on body content:
vcr: { 
  match_requests_on: [:method, :uri, :body],
  record: :new_episodes 
}
Enter fullscreen mode Exit fullscreen mode
  1. Regular Cassette Updates: Periodically re-record your cassettes to ensure they match current API responses:

Troubleshooting

Common Issues

  1. Unmatched Requests: If VCR can't find a matching request, check:

    • Request method (GET, POST, etc.)
    • URL (including query parameters)
    • Request body
    • Headers (if matching on headers)
  2. Playback Failures: If recorded responses aren't playing back:

    • Verify the cassette file exists
    • Check if the request matching criteria are too strict
    • Ensure sensitive data is being filtered consistently

Conclusion

VCR and WebMock provide a robust solution for testing external service integrations in Rails. By following these patterns and best practices, you can create reliable, maintainable tests that don't depend on external services being available.

Remember to:

  • Filter sensitive data
  • Organize cassettes logically
  • Update cassettes periodically
  • Match requests appropriately
  • Handle errors gracefully
. . . . . . . . . . . . .
Terabox Video Player