skip to content

In RSpec, how do --tag filters and config.filter_run_when_matching :focus decide which examples run, and why can a committed fit shrink a CI run?

level: middleimportance: should knowfreq 42%

answer

  1. tags are example metadata
  2. --tag name or name:value
  3. a tilde excludes
  4. applies only if something matches
  5. fit, fdescribe, fcontext

basics

~20 s

--tag slow runs only examples whose metadata has slow set, and --tag ~slow excludes them. filter_run_when_matching :focus narrows a run to focused examples only when some exist, so a fit left in a commit makes CI run just that one.

solid answer

~40 s

Tags are **metadata** on groups and examples: `it "texts the user", :slow` sets `slow: true`, `describe Notifier, type: :service` sets a value. `rspec --tag slow` runs examples with a truthy `slow`, `--tag type:service` matches that value, and a leading `~` turns a tag into an exclusion; several inclusions run examples matching any of them. `config.filter_run_when_matching :focus` is a **conditional** filter: if any example or group has `focus: true`, only those run, otherwise the filter is ignored. `fit`, `fdescribe` and `fcontext` are aliases that add `focus: true`. The danger is the conditional part: a `fit` committed by mistake makes CI run one example and report green. Guard against it, for example by skipping the focus filter when `ENV["CI"]` is set, and consider `config.fail_if_no_examples = true` for runs filtered down to nothing.

code

ruby · 13 lines
ruby
RSpec.configure do |config|
  config.filter_run_when_matching :focus unless ENV["CI"]
end

RSpec.describe NotificationService do
  fit "texts the user" do # focus: true
    # ...
  end

  it "retries on timeout", :slow do
    # ...
  end
end

go deeper

for a junior

Know that tags are metadata like :slow, that --tag slow includes and --tag ~slow excludes, and that fit focuses one example.

for a middle

Explain the difference between filter_run and filter_run_when_matching, how name:value tags and multiple tags combine, and that fit only adds focus: true.

for a senior

Spot the committed-fit risk in CI and add a guard, and design tag-based splits such as a separate slow job without losing coverage.

for a principal

Set team rules for tags such as slow and flaky so filtering stays visible and audited, rather than a quiet way to stop running specs.

## Tags are metadata Every RSpec example group and example carries a **metadata** hash. Extra arguments after the description add to it: ```ruby RSpec.describe NotificationService, type: :service do it "texts the user", :slow do # ... end end ``` A bare symbol such as `:slow` becomes `slow: true`; a pair such as `type: :service` stores that value. Examples inherit metadata from their groups. Filters select examples by looking at this hash. ## Filtering from the command line `--tag` (short `-t`) adds a filter for one run: | Command | Runs | |---|---| | `rspec --tag slow` | examples whose `slow` metadata is truthy | | `rspec --tag type:service` | examples whose `type` equals `service` (compared as strings, so `:service` matches) | | `rspec --tag ~slow` | everything except examples with `slow` set | | `rspec --tag focus --tag ~flaky` | focused examples that are not marked flaky | A few rules from rspec-core 3.13: - the name is always converted to a symbol, and a leading `@` is ignored for Cucumber-style tags; - values `true`, `false` and `nil`, integers, decimals and `:symbols` are parsed; anything else is a String; - several inclusion tags run examples matching **any** of them, and several exclusions skip examples matching any of them; - filters live in a hash, so two filters on the same key keep only the last. ## Filtering from configuration `RSpec.configure` has two ways to include by metadata: 1. **`config.filter_run :focus`** (alias of `filter_run_including`) always applies. If nothing is tagged, nothing runs, and rspec reports `All examples were filtered out`. 2. **`config.filter_run_when_matching :focus`** applies **only if** at least one example matches. With no focused examples, the whole suite runs. The generated `spec_helper.rb` suggests this line. The second exists for temporary focus: tag what you are working on, run `rspec`, and remove the tag when done. rspec-core provides aliases that add `focus: true` for you: `fit`, `fdescribe` and `fcontext` (plus `fspecify` and `fexample`). ## Why a committed fit is dangerous The conditional filter cannot tell a developer's laptop from CI. If a `fit` slips into a commit: 1. CI loads the same `spec_helper.rb` with `filter_run_when_matching :focus`; 2. one example matches, so the filter applies; 3. rspec runs that single example, it passes, and the build is green; 4. every other spec in the suite silently stopped running. Ways to close that gap: - apply the focus filter only outside CI, e.g. `config.filter_run_when_matching :focus unless ENV["CI"]`; - fail the build when focused examples exist, with a code check before rspec runs; - set `config.fail_if_no_examples = true`, which makes rspec exit with a failure status when a run ends up with zero examples (it will not catch the one-`fit` case, but it catches filters that match nothing). ## Other ways to select examples Tags are one of several selectors rspec-core combines: | Selector | Example | |---|---| | file or directory | `rspec spec/services` | | line number | `rspec spec/sms_spec.rb:37` | | example id | `rspec spec/sms_spec.rb[1:5]` | | description substring | `rspec -e "texts the user"` | | description regex | `rspec -E "texts? the user"` | | metadata | `rspec --tag slow` | Line numbers and ids are what `--bisect` and failure summaries print, so they are the usual way to rerun one example. Tags are the durable way to name a category of examples that many runs need to include or skip. ## Common uses of tags - excluding slow or external-service specs locally with `--tag ~slow`, and running them in a separate CI job with `--tag slow`; - selecting spec types, such as `--tag type:service`; - marking known-flaky examples so they can be quarantined and tracked, rather than deleted.

  • What happens if spec_helper.rb uses config.filter_run :focus instead of filter_run_when_matching and nothing is focused?
    `filter_run` always applies, so with no example carrying `focus: true` nothing is selected and rspec reports `All examples were filtered out`. That is why the template suggests `filter_run_when_matching`, which applies the filter only when something matches and otherwise runs the whole suite.
  • How would you run slow specs in a separate CI job without tagging every fast one?
    Tag only the slow examples or groups, for example `:slow`. The fast job runs `rspec --tag ~slow`, which excludes anything with `slow` set, and the slow job runs `rspec --tag slow`, which includes only those. Group-level tags are inherited, so tagging a `describe` covers every example inside it.

saying these in an interview costs you the question

  • filter_run_when_matching :focus runs nothing when no example is focused
  • fit is a different kind of example that skips hooks
  • --tag ~slow runs only the slow examples
  • passing --tag twice requires an example to match both tags
  • a green CI run proves the whole suite ran, even with focus filtering configured