Back to Repositories

Testing VCR Error Handling Implementation in vcr/vcr

This test suite validates VCR’s error handling functionality, specifically focusing on UnhandledHTTPRequestError scenarios. It ensures proper error messaging and configuration options for HTTP request handling in VCR cassettes.

Test Coverage Overview

The test suite provides comprehensive coverage of VCR’s error handling mechanisms, particularly for unhandled HTTP requests. Key areas tested include:

  • Request identification by method and URI
  • Cassette state handling (no cassette, single cassette, multiple cassettes)
  • Configuration options for request matching
  • Different recording modes (:none, :once, :new_episodes)

Implementation Analysis

The testing approach uses RSpec’s describe/context structure to organize test scenarios systematically. It employs helper methods for request creation and message verification, utilizing VCR’s configuration API and cassette functionality. The tests validate both error message content and behavioral aspects of VCR’s error handling system.

Technical Details

Testing tools and setup:

  • RSpec testing framework
  • VCR configuration methods
  • Custom request helper methods
  • Mock interactions for cassette behavior
  • Dynamic message content validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive edge case coverage
  • Isolated test contexts
  • Clear test descriptions
  • DRY helper methods
  • Thorough validation of error messages
  • Proper mocking and stubbing techniques

vcr/vcr

spec/lib/vcr/errors_spec.rb

            
require 'spec_helper'

module VCR
  module Errors
    ::RSpec.describe UnhandledHTTPRequestError do
      def message_for(request_values = {})
        described_class.new(request_with request_values).message
      end
      alias message message_for

      def request_with(options)
        VCR::Request.new(*options.values_at(*VCR::Request.members))
      end

      it 'identifies the request by method and URI' do
        expect(message_for(:method => :post, :uri => 'http://foo.com/')).to include(
          'POST http://foo.com/'
        )
      end

      context 'when there is no cassette' do
        it 'identifies the request by its body when the default_cassette_options include the body in the match_requests_on option' do
          VCR.configuration.default_cassette_options[:match_requests_on] = [:body]

          expect(message_for(:body => 'param=val1')).to include(
            "Body: param=val1"
          )
        end

        it 'identifies the request by its headers when the default_cassette_options include the headers in the match_requests_on option' do
          VCR.configuration.default_cassette_options[:match_requests_on] = [:headers]

          expect(message_for(:headers => { "Content-Type" => "application/json", "X-Custom" => ["123", "ab\"c"]})).to include(
            'Content-Type: "application/json"',
            'X-Custom: "123"',
            'X-Custom: "ab\"c"'
          )
        end

        it 'mentions that there is no cassette' do
          expect(message).to include('There is currently no cassette in use.')
        end

        it 'mentions that the request can be recorded by inserting a cassette' do
          expect(message).to match(/record this request and play it back.*VCR.use_cassette/m)
        end

        it 'mentions the allow_http_connections_when_no_cassette option' do
          expect(message).to include('allow_http_connections_when_no_cassette')
        end

        it 'mentions that the request can be ignored' do
          expect(message).to include('set an `ignore_request` callback')
        end

        it 'does not double-insert the asterisks for the bullet points' do
          expect(message).not_to match(/\s+\*\s+\*/)
        end

        it 'mentions the debug logging configuration option' do
          expect(message).to include('debug_logger')
        end
      end

      context 'when there are cassettes' do
        it 'identifies the request by its body when the match_requests_on option includes the body' do
          VCR.use_cassette('example', :match_requests_on => [:body]) do
            expect(message_for(:body => 'param=val1')).to include(
              "Body: param=val1"
            )
          end
        end

        it 'identifies the request by its headers when the match_requests_on option includes the headers' do
          VCR.use_cassette('example', :match_requests_on => [:headers]) do
            expect(message_for(:headers => { "Content-Type" => "application/json", "X-Custom" => ["123", "ab\"c"]})).to include(
              'Content-Type: "application/json"',
              'X-Custom: "123"',
              'X-Custom: "ab\"c"'
            )
          end
        end

        it 'does not identify the request by its body when the cassette match_requests_on option does not include the body but the default_cassette_options do' do
          VCR.configuration.default_cassette_options[:match_requests_on] = [:body]
          VCR.use_cassette('example', :match_requests_on => [:uri]) do
            expect(message_for(:body => 'param=val1')).to_not match(/body/i)
          end
        end

        it 'mentions the details about the single cassette when there is one cassette' do
          VCR.use_cassette('example') do
            expect(message).to match(/VCR is currently using the following cassette:.+example.yml/m)
          end
        end

        it 'mentions the details about all cassettes when there are a few cassettes' do
          VCR.use_cassette('example') do
            VCR.use_cassette('sample') do
              expect(message).to match(/VCR are currently using the following cassettes:.+sample.yml.+example.yml/m)
            end
          end
        end

        it 'mentions that :new_episodes can be used to record the request' do
          VCR.use_cassette('example') do
            expect(message).to include('use the :new_episodes record mode')
          end
        end

        it 'mentions that :once does not allow a cassette to be re-recorded' do
          VCR.use_cassette('example', :record => :once) do
            expect(message).to include('(:once) does not allow new requests to be recorded')
          end
        end

        it 'mentions that :none does not allow any recording' do
          VCR.use_cassette('example', :record => :none) do
            expect(message).to include('(:none) does not allow requests to be recorded')
          end
        end

        it 'mentions that one of the files does not exist when using :none and file does not exist' do
          VCR.use_cassette('example', :record => :none) do
            expect(message).to include('One or more cassette names registered was not found.')
          end
        end

        it 'mentions legacy message when file exists' do
          VCR.configure do |c|
            c.cassette_library_dir = File.join(VCR::SPEC_ROOT, 'fixtures')
          end
          VCR.use_cassette('fake_example_responses', :record => :none) do
            expect(message).to include('can temporarily change the record mode to :once, delete the cassette file')
          end
        end

        it 'does not mention the :once or :none record modes if using the :new_episodes record mode' do
          VCR.use_cassette('example', :record => :new_episodes) do
            expect(message).not_to include(':once', ':none')
          end
        end

        it 'does not mention the :once or :none record modes if using the :new_episodes record mode at least in one cassette' do
          VCR.use_cassette('example', :record => :new_episodes) do
            VCR.use_cassette('sample') do
              expect(message).not_to include('current record mode (:once)', 'current record mode (:none)')
            end
          end
        end

        it 'mentions :allow_playback_repeats if the cassette has a used matching interaction' do
          VCR.use_cassette('example') do |cassette|
            expect(cassette.http_interactions).to respond_to(:has_used_interaction_matching?)
            allow(cassette.http_interactions).to receive(:has_used_interaction_matching?).and_return(true)
            expect(message).to include('allow_playback_repeats')
          end
        end

        it 'does not mention :allow_playback_repeats if the cassette does not have a used matching interaction' do
          VCR.use_cassette('example') do |cassette|
            expect(cassette.http_interactions).to respond_to(:has_used_interaction_matching?)
            allow(cassette.http_interactions).to receive(:has_used_interaction_matching?).and_return(false)
            expect(message).not_to include('allow_playback_repeats')
          end
        end

        it 'does not mention using a different :match_requests_on option when there are no remaining unused interactions' do
          VCR.use_cassette('example') do |cassette|
            expect(cassette.http_interactions).to respond_to(:remaining_unused_interaction_count)
            allow(cassette.http_interactions).to receive(:remaining_unused_interaction_count).and_return(0)
            expect(message).not_to include('match_requests_on cassette option')
          end
        end

        it 'mentions using a different :match_requests_on option when there are some remaining unused interactions' do
          VCR.use_cassette('example') do |cassette|
            expect(cassette.http_interactions).to respond_to(:remaining_unused_interaction_count)
            allow(cassette.http_interactions).to receive(:remaining_unused_interaction_count).and_return(1)
            expect(message).to include('match_requests_on cassette option')
          end
        end

        it 'uses the singular (HTTP interaction) when there is only 1 left' do
          VCR.use_cassette('example') do |cassette|
            expect(cassette.http_interactions).to respond_to(:remaining_unused_interaction_count)
            allow(cassette.http_interactions).to receive(:remaining_unused_interaction_count).and_return(1)
            expect(message).to include('1 HTTP interaction ')
          end
        end

        it 'uses the plural (HTTP interactions) when there is more than 1 left' do
          VCR.use_cassette('example') do |cassette|
            expect(cassette.http_interactions).to respond_to(:remaining_unused_interaction_count)
            allow(cassette.http_interactions).to receive(:remaining_unused_interaction_count).and_return(2)
            expect(message).to include('2 HTTP interactions ')
          end
        end

        it 'mentions the debug logging configuration option' do
          VCR.use_cassette('example', :record => :new_episodes) do
            expect(message).to include('debug_logger')
          end
        end
      end
    end
  end
end