skip to content

In a Rakefile, what does RSpec::Core::RakeTask.new(:spec) define, and what happens to the rake run when examples fail?

level: middleimportance: nice to knowfreq 22%

answer

  1. a task named spec by default
  2. shells out to the rspec executable
  3. pattern, exclude_pattern, rspec_opts
  4. fail_on_error defaults to true
  5. rescue LoadError where rspec is absent

basics

~10 s

RSpec::Core::RakeTask.new(:spec) defines a rake task that runs the rspec executable in a subprocess over spec/{,/*/}/*_spec.rb. If examples fail, fail_on_error (true by default) makes rake exit with rspec's exit status.

solid answer

~40 s

`require "rspec/core/rake_task"` and `RSpec::Core::RakeTask.new(:spec)` define a task named `spec` (the default name) described as `Run RSpec code examples`. It builds a command, `ruby` plus `ruby_opts`, the rspec-core load path, the `rspec` executable, `--pattern` and `rspec_opts`, prints it (`verbose` is true) and runs it with `system`. The default `pattern` is `spec/**{,/*/**}/*_spec.rb`; `exclude_pattern`, `rspec_opts` (such as `"--tag ~slow"`) and a `SPEC` environment variable change what runs. When rspec exits non-zero, `fail_on_error`, true by default, prints the failed command and exits rake with the same status, so a `default` task depending on `spec` fails the build. The rspec-core docs suggest wrapping the require in `rescue LoadError` so the Rakefile still loads where rspec is not installed.

go deeper

for a junior

Recall that RSpec::Core::RakeTask.new(:spec) gives you rake spec, and that task default: :spec makes plain rake run the suite.

for a middle

Explain that the task shells out to the rspec executable, which attributes change the command, and how fail_on_error turns failed examples into a failed rake run.

for a senior

Use separate rake tasks with rspec_opts or pattern to split suites in CI, and recognise when a disabled fail_on_error lets a red suite pass a build.

for a principal

Decide whether CI calls rspec directly or through rake, keeping one documented entry point so local and CI runs use the same options.

## What the task is rspec-core ships a class that defines a Rake task for running the suite: ```ruby begin require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) do |t| t.rspec_opts = "--tag ~slow" end task default: :spec rescue LoadError # rspec is not installed here, for example in production end ``` `RSpec::Core::RakeTask.new(:spec)` defines a task called `spec`; with no argument the name is also `:spec`. Unless you give it your own `desc`, it is described as `Run RSpec code examples`, so it shows up in `rake -T`. The optional block receives the task object and sets its attributes. ## How it runs the suite The task does not run specs inside the rake process. It builds a shell command and runs it with `system`: 1. the Ruby interpreter, plus any `ruby_opts`; 2. `-I` with the load paths of rspec-core and rspec-support; 3. the `rspec` executable from the loaded rspec-core gem (`rspec_path`); 4. the file selection: `--pattern` with `pattern`, or the files listed in the `SPEC` environment variable; 5. `--exclude-pattern` if `exclude_pattern` is set; 6. `rspec_opts`, appended last. Because `verbose` defaults to true, rake prints that command before running it, which makes CI logs show exactly what ran. ## The attributes | Attribute | Default | Purpose | |---|---|---| | `name` | `:spec` | task name | | `pattern` | `spec/**{,/*/**}/*_spec.rb` | files to load | | `exclude_pattern` | `nil` | files to skip | | `rspec_opts` | `nil` | extra rspec options, such as `--tag ~slow` | | `ruby_opts` | `nil` | options for the Ruby interpreter | | `fail_on_error` | `true` | exit rake with rspec's status on failure | | `failure_message` | `nil` | text printed when rspec fails | | `verbose` | `true` | print the command | Options in `.rspec` and `spec_helper.rb` still apply, because the subprocess is an ordinary rspec run. ## What happens when examples fail rspec exits with status 1 when examples fail (changeable with `--failure-exit-code`). The task then: - prints `failure_message`, if one is set; - with `fail_on_error` true, prints the failed command to standard error and calls `exit` with rspec's exit status; - with `fail_on_error` false, returns normally, so later tasks keep running and rake exits successfully. The default is what CI needs: `rake` fails when the suite fails. Turning it off is occasionally useful for a task that must always continue, such as one that collects reports, but it hides failures from anything checking rake's status. ## Several tasks from one class Because each `RakeTask.new` defines an independent task, teams often split suites: - `RSpec::Core::RakeTask.new(:fast) { |t| t.rspec_opts = "--tag ~slow" }`; - `RSpec::Core::RakeTask.new(:slow) { |t| t.rspec_opts = "--tag slow" }`; - `RSpec::Core::RakeTask.new(:services) { |t| t.pattern = "spec/services/**/*_spec.rb" }`. ## Rake task or plain rspec? | | `rake spec` | `rspec` directly | |---|---|---| | Process | rake, then a child rspec process | one rspec process | | File selection | `pattern` or `SPEC` | arguments, `--pattern`, `.rspec` | | Extra options | `rspec_opts` in the Rakefile | on the command line | | Fits into | `task default: :spec` and other rake chains | shell scripts and CI steps | | Failure | exits with rspec's status (default) | exits with rspec's status | Neither is more correct. Rake is convenient when running specs is one step of a larger `rake` chain; plain `rspec` is simpler when it is the whole job. What matters is that local runs and CI use the same entry point, so options do not drift between them. ## Why the rescue LoadError The rspec-core documentation recommends the `begin`/`rescue LoadError` wrapper so the Rakefile can still be loaded in an environment where rspec is not installed, for example a production server that runs other rake tasks. Without it, every rake command there fails at the `require`.

  • How can one run of the rake task target a single spec file without editing the Rakefile?
    Set the `SPEC` environment variable, as in `SPEC=spec/sms_spec.rb rake spec`. When `SPEC` is set, the task passes those files to rspec instead of its `pattern`. Extra rspec options can come from `SPEC_OPTS`, which rspec itself reads.
  • What changes if a CI job sets fail_on_error = false on the spec task?
    When examples fail, the task no longer calls `exit` with rspec's status; it returns normally, so rake continues and exits 0. The job then reports success unless something else inspects the rspec output. It should only be used where a later step is designed to judge the results.

saying these in an interview costs you the question

  • the rake task runs specs inside the rake process itself
  • a rake task ignores .rspec and spec_helper.rb settings
  • fail_on_error defaults to false, so failures need extra setup to fail CI
  • the default pattern only matches files directly under spec/
  • rescue LoadError around the require hides failing specs