Skip to content

Interactive Approval Testing for RSpec

License

Notifications You must be signed in to change notification settings

DannyBen/rspec_approvals

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RSpec Approvals

Gem Version Build Status Maintainability


RSpec Approvals allows you to interactively review and approve testable content.

Demo


Install

$ gem install rspec_approvals

Or with bundler:

gem 'rspec_approvals'

Usage

Require the gem in your spec helper:

# spec/spec_helper.rb
require 'rspec_approvals'

And use any of the matchers in your specs.

describe 'ls' do
  it "works" do
    expect(`ls`).to match_approval('ls_approval')
  end
end

Matchers

match_approval - Compare Strings

Compare a string with a pre-approved approval.

expect('some string').to match_approval('approval_filename')

output_approval - Compare STDOUT/STDERR

Compare an output (stdout or stderr) with a pre-approved approval.

expect { puts "hello" }.to output_approval('approval_filename')
expect { puts "hello" }.to output_approval('approval_filename').to_stdout
expect { $stderr.puts "hello" }.to output_approval('approval_filename').to_stderr

# The first two are the same, as the default stream is stdout.

raise_approval - Compare raised exceptions

Compare a raised exception with a pre-approved approval.

expect { raise 'some error' }.to raise_approval('approval_filename')

Modifiers

diff - String similarity

Adding diff(distance) to either match_approval or output_approval will change the matching behavior. Instead of expecting the strings to be exactly the same, using diff compares the strings using the Levenshtein distance algorithm.

In the below example, we allow up to 5 characters to be different.

expect ('some string').to match_approval('approval_filename').diff(5)
expect { puts 'some string' }.to output_approval('approval_filename').diff(5)

except - Exclude by regular expression

Adding except(regex) to either match_approval or output_approval will modify the string under test before running. By default, the regular expression will be replaced with ....

In the below example, we ignore the full path of the file.

expect('path: /path/to/file').to match_approval('approval_filename').except(/path: .*file/)

You may provide a second argument, which will be used as an alternative replace string:

In the below example, all time strings will be replaced with HH:MM:

expect('22:30').to match_approval('approval_filename').except(/\d2:\d2/, 'HH:MM')

before - Alter the string before testing

The before(proc) method is a low level method and should normally not be used directly (as it is used by the except modifier).

Adding before(proc) to either match_approval or output_approval will call the block and supply the actual string. The proc is expected to return the new actual string.

In the below example, we replace all email addresses in a string.

expect('hello [email protected]').to match_approval('approval_filename').before ->(actual) do
  actual.gsub /\w+@\w+\.\w+/, '[email protected]'
end

Configuration

interactive_approvals

By default, interactive approvals are enabled in any environment that does not define the CI or the GITHUB_ACTIONS environment variables. You can change this by adding this to your spec_helper

RSpec.configure do |config|
  config.interactive_approvals = false # or any logic
end

approvals_path

By default, approvals are stored in spec/approvals. To change the path, add this to your spec_helper.

RSpec.configure do |config|
  config.approvals_path = 'spec/anywhere/else'
end

auto_approve

If you wish to automatically approve all new or changed approvals, you can set the auto_approve configuration option to true. By default, auto approval is enabled if the environment variable AUTO_APPROVE is set.

RSpec.configure do |config|
  config.auto_approve = true # or any logic
end

This feature is intended to help clean up the approvals folder from old, no longer used files. Simply run the specs once, to ensure they all oass, delete the approvals folder, and run the specs again with:

$ AUTO_APPROVE=1 rspec

strip_ansi_escape

In case your output strings contain ANSI escape codes that you wish to avoid storing in your approvals, you can set the strip_ansi_escape to true.

RSpec.configure do |config|
  config.strip_ansi_escape = true
end

before_approval

In case you need to alter the actual output globally, you can provide the before_approval option with a proc. The proc will receive the actual output - similarly to the before modifier - and is expectedd to return a modified actual string.

RSpec.configure do |config|
  config.before_approval = ->(actual) do
    # return the actual string, without IP addresses
    actual.gsub(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, '[IP REMOVED]')
  end
end

Advanced Usage Tips

Sending output directly to RSpecApprovals

In some cases, you might need to send output directly to the RSpecApproval stream capturer.

An example use case, is when you are testing Logger output.

The RSpecApproval#stdout and RSpecApproval#stderr can be used as an alternative to $stdout and $stderr. These methods both return the StringIO object that is used by RSpecApprovals to capture the output.

For example, you can use this:

logger = Logger.new(RSpecApprovals.stdout)

as an alternative to this:

logger = Logger.new($stdout)

Consistent terminal width

In case you are testing standard output with long lines, you may encounter inconsistencies when testing on different hosts, with varying terminal width. In order to ensure consistent output to stdout, you may want to set a known terminal size in your spec_helper:

ENV['COLUMNS'] = '80'
ENV['LINES'] = '24'

Recommended rspec flags

For best results, it is recommended you configure rspec to use the documentation format. Place the code below in a file named .rspec in your project's directory:

--color
--format documentation

Contributing / Support

If you experience any issue, have a question or a suggestion, or if you wish to contribute, feel free to open an issue.