Back to Repositories

Implementing RSpec Metadata Integration for VCR Cassette Management in vcr

This test suite implements RSpec integration with VCR, providing automated cassette management for HTTP interaction recording and playback in Ruby tests. It handles metadata-driven cassette naming and configuration for seamless VCR integration with RSpec test blocks.

Test Coverage Overview

The test suite provides comprehensive coverage for VCR’s RSpec integration, focusing on cassette management and metadata handling.

  • Cassette name generation from RSpec metadata and descriptions
  • Support for various VCR configuration options through test metadata
  • Handling of nested example groups for hierarchical cassette naming
  • Edge case handling for empty descriptions and scoped IDs

Implementation Analysis

The implementation uses RSpec’s metadata system to configure VCR cassettes declaratively. The code employs Ruby metaprogramming techniques for dynamic cassette naming and configuration.

  • Metadata-driven cassette naming strategy
  • Support for hash-based and string-based configuration options
  • Automatic cassette insertion and ejection through RSpec hooks
  • Exception-aware cassette handling

Technical Details

  • RSpec configuration hooks and callbacks
  • VCR cassette management API integration
  • Ruby module extension patterns
  • Metadata extraction and processing utilities
  • Dynamic configuration based on test context

Best Practices Demonstrated

The test implementation showcases several testing best practices for Ruby and RSpec integration.

  • Clean separation of concerns between VCR and RSpec integration
  • Flexible configuration options through metadata
  • Automatic resource management with before/after hooks
  • Descriptive and hierarchical cassette naming
  • Graceful error handling and cleanup

vcr/vcr

lib/vcr/test_frameworks/rspec.rb

            
module VCR
  # Integrates VCR with RSpec.
  module RSpec
    # @private
    module Metadata
      extend self

      def vcr_cassette_name_for(metadata)
        description = 
          if metadata[:description].empty?
            # we have an "it { is_expected.to be something }" block
            metadata[:scoped_id]
          else
            metadata[:description]
          end
        example_group = 
          if metadata.key?(:example_group)
            metadata[:example_group]
          else
            metadata[:parent_example_group]
          end

        if example_group
          [vcr_cassette_name_for(example_group), description].join('/')
        else
          description
        end
      end

      def configure!
        ::RSpec.configure do |config|

          when_tagged_with_vcr = { :vcr => lambda { |v| !!v } }

          config.before(:each, when_tagged_with_vcr) do |ex|
            example = ex.respond_to?(:metadata) ? ex : ex.example

            cassette_name = nil
            options = example.metadata[:vcr]
            options = case options
                      when Hash #=> vcr: { cassette_name: 'foo' }
                        options.dup
                      when String #=> vcr: 'bar'
                        cassette_name = options.dup
                        {}
                      else #=> :vcr or vcr: true
                        {}
                      end

            cassette_name ||= options.delete(:cassette_name) ||
                              VCR::RSpec::Metadata.vcr_cassette_name_for(example.metadata)
            VCR.insert_cassette(cassette_name, options)
          end

          config.after(:each, when_tagged_with_vcr) do |ex|
            example = ex.respond_to?(:metadata) ? ex : ex.example
            VCR.eject_cassette(:skip_no_unused_interactions_assertion => !!example.exception)
          end
        end
      end
    end
  end
end