Avoid Merging RSpec "Focused" Tests

Stephanie Viccari

I enjoy using the RSpec Focus filter to run a specific test or subset of tests. However, I often forget to remove this filter, which leaves the tests in a “focused” state. On a good day, I notice the issue and remove the filter. On a bad day, I don’t notice the issue and merge my changes, leaving RSpec in a “focused” state and causing CI to ignore the majority of the test suite.

Gif of a grey cat, sitting on a window sill in a trance, focused
on something in front of them as a person waves their hand in front of the cat's
eyes, while the cat ignores their hand and continues to stare off into the distance.

Before we restore RSpec’s attention, let’s review how to use the RSpec Focus filter.

# run a specific test by adding the "focus" metadata
it "tells RSpec to run this test and this test only", focus: true do
end

# use the alias `fit`, `fdescribe`, or `fcontext`
fit "tells RSpec to run this test and this test only" do
end

To execute focused tests, use either of the following commands:

rspec

rspec --tag focus

Option 1: Use RuboCop to Catch Focused Tests

RuboCop provides an RSpec extension that checks for focused tests. If your team already uses RuboCop, follow the rubocop-rspec installation instructions and RuboCop will notify you when a focused test exists.

Run RuboCop for each Pull Request

Running RuboCop locally is great, but it’s still possible that our future self (or colleagues) will forget to run RuboCop locally and commit a focused test. To prevent focused tests from being merged, we can include RuboCop as part of the Pull Request process.

Using GitHub as an example, let’s create a GitHub Action to run Rubocop and a Branch Protection Rule to prevent the Pull Request from being merged.

  1. Add a GitHub Action that runs RuboCop:
# github/workflows/rubocop.yml

name: RuboCop
on:
  pull_request:
    branches:
      - main
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        bundler-cache: true
    - name: Run Rubocop
      run: bundle exec rubocop
  1. Add a GitHub Branch Protection Rule

To add a rule, visit “Settings”, then “Branches”, and then select the option to add a rule. From here, set the “Branch name pattern” to name of the branch you wish to protect. For example, if you wish to protect the “main” branch, set the branch name pattern to “main”.

Next, enable the “Require status checks to pass before merging” option. In the “Search for status checks” field, search for the name of the job (in this example, the name of the job is “lint”). If you run into any issues, consult the GitHub documentation: About branch protection rules.

Image that shows a GitHub form with the option "Require status checks to pass before merging" selected.

Once the GitHub Action and Branch Protection Rule are completed, attempting to merge a Pull Request that has not successfully passed RuboCop will look like this:

Screenshot of a GitHub Pull Request that includes the text "All checks have failed" and the "Merge pull request" button is disabled.

Use the RuboCop RSpec Extension in Isolation

If your team uses a tool other than RuboCop, you can still use the rubocop-rspec extension without adding all of RuboCop’s opinions. Steps below.

  1. Follow the rubocop-rspec installation instructions
  2. At the root of your project, create a new file named .rubocop.yml
  3. In the .rubocop.yml file, disable default warnings and enable the RSpec Focus warning. Your .rubocop.yml file will look something like this:
require: rubocop-rspec

# Turn all Rubocop rules off by default
AllCops:
  DisabledByDefault: true

# Turn on the RSpec/Focus rule
RSpec/Focus:
  Enabled: true

Option 2: Use RSpec to Catch Focused Tests

Another option is to instruct RSpec to raise when a test includes the Focus metadata. (Thank you to Marz Drel for this suggestion.)

# spec/rails_helper.rb

RSpec.configure do |config|
  if ENV["CI"]
    config.before(:example, :focus) { |example| raise "Focused spec found at
#{example.location}" }
  else
    config.filter_run_when_matching :focus
  end
end

If you prefer the RSpec approach, don’t forget to include “green tests” as part of your Pull Request process. ✅

Hooray! Now, using RuboCop or RSpec, we can alert the team when a Focus filter needs to be removed and ensure all of our tests are hard at work.

Gif of an orange kitten, sitting on an open laptop and nodding their head, creating the impression that they're reading the content on the screen.