RSpec: Marking sections in long examples

Posted . Visible to the public.

RSpec examples can get quite long, especially in feature specs. This makes them hard to read & understand. Also, when executing them, it may take seconds to see any progress.

To improve this, I have successfully been using a little "step" helper in my tests. It marks semantic sections, structuring an example while improving documentation. When the test runs, each step prints its message (or a dot, depending on your formatter).

# spec/support/step_helper.rb
module StepHelper

  # Use this helper to label groups of related actions in long specs.
  # This helps understanding a spec, as well as tracking progress during execution.
  #
  # Example:
  # it 'offers a login' do
  #   # Test setup
  #
  #   STEP 'Attempt login with wrong credentials'
  #   # ...
  #
  #   STEP 'Successful login'
  #   # ...
  # end
  def STEP(label)
    formatters = RSpec.configuration.formatters.map(&:class)
    example = RSpec.current_example
    hierarchy = example.example_group.parent_groups
    indent = '  ' * hierarchy.count

    if formatters.include? RSpec::Core::Formatters::DocumentationFormatter
      puts "#{indent}⮦ #{label}"
    elsif formatters.include? RSpec::Core::Formatters::ProgressFormatter
      print '.'
    else
      raise "Unexpected RSpec formatters: #{formatters.map(&:name)}"
    end
  end

end

RSpec.configure do |config|
  config.include StepHelper
end

Usage example

  scenario 'The list of advisories is paginated' do
    elements_per_page(DepReporter::Advisory, 2)
    create(:npm_advisory, title: 'oldest', created_at: '2019-05-01')
    create(:npm_advisory, title: 'advisory', created_at: '2019-06-01')
    create(:npm_advisory, title: 'latest', created_at: '2019-07-01')

    STEP 'visit the first page of advisories'
    visit advisories_path
    expect(page).to have_content('latest')
      .and have_content('advisory')
      .and have_no_content('oldest')

    STEP 'navigate to the second page'
    click_link '2'
    expect(page).to have_content('oldest')
      .and have_no_content('latest')
      .and have_no_content('advisory')
  end
Dominik Schöler
Last edit
Dominik Schöler
License
Source code in this card is licensed under the MIT License.
Posted by Dominik Schöler to makandra dev (2025-08-22 13:56)