Matthew

Snapshot testing in Ruby with Thor

October 20, 2025

It didn't seem like an efficient use of time to write unit tests in the early development stages of jsonapi-resources-anchor.

I still wanted to test my code, though.

The primary function of the gem is to generate schema files. That felt like a good candidate for snapshot testing.

Seeing ElMassimo/types_from_serializers also use snapshot testing was encouraging.

But with simple comparison assertions, the process of:

  • make code change
  • run Rake task to regenerate and overwrite existing files
  • check git diff to ensure changes are expected
  • changes aren't expected? => git checkout .
  • do it all over again

felt a little tedious.

I wanted something more like Jest's snapshot testing experience.

I recalled that rails app:update prompted you to accept or reject proposed patches to files and allowed you to view the diff in the CLI. Maybe I could use whatever powered that?

Turns out it was rails/thor.

It was surprisingly easy to integrate into the test suite. Now, we can choose to get prompted when there's a diff.

Demo and code below.

THOR_MERGE=nvim bundle exec rspec
require "thor"
 
class SnapshotUpdate < Thor
  include Thor::Actions
 
  def self.prompt(...) = new.prompt(...)
 
  desc "prompt", "Prompt user to update snapshot"
  def prompt(...) = create_file(...)
end
 
RSpec.describe Anchor::TypeScript::SchemaGenerator do
  it "generates correct schema" do
    schema = described_class.call(register: Schema.register)
    path = Rails.root.join("test/files", "schema.ts")
 
    # if schema.ts doesn't exist, just write to it
    File.open(path, "w") { |file| file.write(schema) } unless File.file?(path)
 
    if ENV["THOR_MERGE"] && File.read(path) != schema
      SnapshotUpdate.prompt(path, schema)
    end
 
    expect(File.read(path)).to eql(schema)
  end
end

Next step is to make this a custom RSpec matcher to have a more convenient API like expect(schema).to match_snapshot("schema.ts") in levinmr/rspec-snapshot.