DEV Community

David Paluy
David Paluy

Posted on

2

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

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

Image of Wix Studio

2025: Your year to build apps that sell

Dive into hands-on resources and actionable strategies designed to help you build and sell apps on the Wix App Market.

Get started

Top comments (1)

Collapse
 
eth3rnit3 profile image
Eth3rnit3

Super tuto, thank you very much ;)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay