Blog

Thoughts from my daily grind

Detecting Unused VCR Cassettes

Posted by Ziyan Junaideen |Published: 07 October 2022 |Category: Ruby on Rails
Default Upload |

In the past 10 years, since I migrated to Ruby land, VCR has been one of the most frequently used tools in my toolbox. It allows us to easily cache 3rd party API responses (in VCR cassettes in YAML format) in tests so that we don't have to manually mock the responses with WebMock.

Usually, managing VCR cassettes is not a difficult task. But being a code reviewer, I'd noticed some PRs coming in with unused VCR cassettes and wanted a way for developers (including myself) to easily identify unused VCR cassettes.

Credits: This is not my own work. I picked it up in a GH Gist IIRC but only after many searches. But now I have misplaced the URL. I will update this post giving due credit to the author.

Features

  • Runs only when profiling is enabled in RSpec rspec -p
  • Registers cassette usage by hooking into VCR
  • Prints the difference of cassettes in the cassettes folder and the executed cassettes

Gotchas

  • Will not recognise cassettes included in xit, xdescribe etc as these examples will be skipped.
  • May give a false positive if a check fails before the VCR is called

Code

I have setup RSpec such that all files in the spec/support folder to be auto required.

# File: spec/support/unused_cassette_formatter.rb

class UnusedCassetteFormatter
  def initialize(output)
    @output = output
  end

  def dump_profile(_)
    return if (unused = list_unused).blank?

    print(unused)
  end

  def self.use(cassette_name, options = {})
    used_cassettes << VCR::Cassette.new(cassette_name, options).file
  end

  def self.used_cassettes
    @used_cassettes ||= Set.new
  end

  private

  def print(unused)
    @output.puts("\nUnused cassettes:")
    unused.each do |f|
      relative_path = f.sub(Rails.root.to_s, '')
      @output.puts("  ⚠️  #{relative_path}")
    end
  end

  def list_unused
    all = Dir[File.join(VCR.configuration.cassette_library_dir, '**/*.yml')].map do |d|
      File.expand_path(d)
    end

    all - self.class.used_cassettes.to_a
  end
end

RSpec.configure do |config|
  unless config.files_to_run.one?
    config.reporter.register_listener(UnusedCassetteFormatter.new(config.output_stream), :dump_profile)
  end
end

module CassetteReporter
  def insert_cassette(name, options = {})
    UnusedCassetteFormatter.use(name, options)
    super
  end
end

VCR.extend(CassetteReporter)

Now when running rspec -p you will have a response like this.

rspec -p

Unused cassettes:
  ⚠️  /spec/cassettes/purchase/bad_response.yml
  ⚠️  /spec/cassettes/purchase/bad_avs.yml
  ⚠️  /spec/cassettes/reveral/purchase.yml
About the Author

Ziyan Junaideen -

Ziyan is an expert Ruby on Rails web developer with 8 years of experience specializing in SaaS applications. He spends his free time he writes blogs, drawing on his iPad, shoots photos.

Comments