Support rails generator testing in Rspec

This goes into spec/support/generator_specs_support.rb:

module GeneratorSpecsSupport
  extend ActiveSupport::Concern

  included do
    gen_name = @metadata[:description]
    gen_name_snake = gen_name.underscore

    require 'fileutils'
    require 'rails/generators'
    require "generators/#{gen_name_snake.chomp('_generator')}/#{gen_name_snake}"
    require 'active_support/testing/stream'
    require 'rails/generators/testing/behaviour'

    include FileUtils
    include ActiveSupport::Testing::Stream

    around(:each) do |example|
      current_path = File.expand_path(Dir.pwd)
      Dir.mktmpdir do |path|
        @destination_root = path
        @generator_class = gen_name.constantize
        @default_arguments = []
        example.run
        remove_instance_variable('@destination_root')
        remove_instance_variable('@generator_class')
        remove_instance_variable('@default_arguments')
      end
      cd current_path
    end
  end

  attr_reader :destination_root, :generator_class, :default_arguments
  def generated_path(path) = Pathname("#{destination_root}/#{path}")

  # Borrowed from railties/lib/rails/generators/testing/behaviour.rb
  def run_generator(args = default_arguments, config = {})
    capture(:stdout) do
      args += ["--skip-bundle"] unless args.include?("--no-skip-bundle") || args.include?("--dev")
      args |= ["--skip-bootsnap"] unless args.include?("--no-skip-bootsnap")

      generator_class.start(args, config.reverse_merge(destination_root: destination_root))
    end
  end
end

In spec/rails_helper.rb add the following:

# Near top requires:
require Rails.root.join('spec/support/generator_specs_support')

# In config block:
config.include(GeneratorSpecsSupport, type: :generator)

Specs should go into spec/generator/my_generator_spec.rb.

Here’s an example spec:

require 'rails_helper'

# `'AdrGenerator'` must be the actual class name of the generator.
RSpec.describe 'AdrGenerator' do
  it "inserts correct name and date" do
    today = Date.current
    expect { run_generator ["sample_adr"] }.to_not raise_error

    md_file = generated_path "doc/adr/#{today.strftime("%Y-%m")}-sample-adr.md"
    expect(md_file).to exist

    md_contents = md_file.read
    expect(md_contents).to include("# Sample Adr")
    expect(md_contents).to include("* **Last Updated:** #{today}")
  end
end

Code snippets in this post are covered by MIT License.



Date
June 16, 2023