RSpec for Beginners: Key Practices and Techniques

Key Practices for Effective Testing

1. Logical Structuring with describe and context

Think of describe and context as ways to organize your tests into logical groups.

  • describe: Used to describe the overall feature or class you’re testing.
  • context: Used to describe specific scenarios or conditions within that feature.

Example:

RSpec.describe 'Course' do
  context 'when a student enrolls' do
    # Tests related to student enrollment
  end

  context 'when an assignment is created' do
    # Tests related to assignment creation
  end
end

This structure makes it easy to understand what each test is about and where to find it.

2. Clear Expectations: Testing at the Right Level

Each test should focus on a specific behavior or outcome. Avoid testing too many things at once.

Example:

Instead of:

it 'should enroll a student and update the course count' do
  # ... complex logic ...
end

Prefer:

it 'should enroll a student' do
  # ... test student enrollment ...
end

it 'should update the course count' do
  # ... test course count update ...
end

This makes your tests more focused and easier to debug.

3. Shared Setup with let and before

Avoid repeating code by using let and before.

  • let: Defines a variable that is lazily evaluated (only when used).
  • before: Executes code before each example within a describe or context block.

Example:

RSpec.describe 'Assignment' do
  let(:student) { Student.new(name: 'Alice') }
  let(:course) { Course.new(name: 'Math 101') }
  let(:assignment) { Assignment.new(course: course, title: 'Homework 1') }

  before do
    course.enroll(student)
  end

  it 'should be associated with a course' do
    expect(assignment.course).to eq(course)
  end

  it 'should be accessible to enrolled students' do
    expect(student.assignments).to include(assignment)
  end
end

Here, let sets up the student, course, and assignment, and before ensures the student is enrolled in the course before each test.

4. Documentation Output: Tests as Living Documentation

RSpec can generate detailed output that reads like documentation. This helps you understand the behavior of your code.

5. Focused Execution: Running Specific Tests

You don’t always need to run all tests. RSpec lets you run specific subsets of tests, which is very useful during development.

6. Iterative Debugging: Quickly Re-running Failed Specs

When a test fails, you can quickly re-run it to debug the issue.

7. Progress Marking: Managing Work in Progress with pending

Use pending to mark tests that are not yet implemented or are failing due to known issues.

Customizing RSpec Output

The Progress Formatter

The default output uses dots (.) for passing tests and F for failures. It’s good for a quick overview.

$ rspec
..F.
Failures:
  1) ...
4 examples, 1 failure

The Documentation Formatter

This formatter provides detailed output, resembling project documentation. It uses indentation to show nesting from describe and context blocks.

How to use:

$ rspec --format documentation
# or
$ rspec -f d

Example Output:

Course
  when a student enrolls
    should be able to enroll a student
    should increase the number of enrolled students
  when an assignment is created
    should be associated with a course
    should be accessible to enrolled students (FAILED - 1)

Failures:
  1) Course when an assignment is created should be accessible to enrolled students
     Failure/Error: expect(student.assignments).to include(assignment)
       expected `[#<Assignment:0x000000010f123456 @course=#<Course:0x000000010e987654 @name="Math 101", @students=[#<Student:0x000000010d543210 @name="Alice">]>, @title="Homework 1">]` to include #<Assignment:0x000000010f123456 @course=#<Course:0x000000010e987654 @name="Math 101", @students=[#<Student:0x000000010d543210 @name="Alice">]>, @title="Homework 1">
     # ./spec/assignment_spec.rb:20:in `block (3 levels) in <top (required)>'

Syntax Highlighting

Use the coderay gem for color-coding Ruby code within test output. This makes it easier to read failing spec output.

How to install:

gem install coderay -v 1.1.1

RSpec will automatically use CodeRay if it’s available.

Identifying Slow Examples

Use the --profile option to identify the slowest specs.

Example:

$ rspec --profile 2

This will list the two slowest examples.

Running Specific Tests

By File or Directory

  • Run all specs in a directory: $ rspec spec/unit
  • Run a specific spec file: $ rspec spec/unit/assignment_spec.rb
  • Run multiple directories or files: $ rspec spec/unit spec/smoke spec/foo_spec.rb

By Name

Use the --example or -e option to filter examples by name.

Example:

$ rspec -e enroll -fd

This will run examples that contain “enroll” in their description.

Specific Failures

Run a specific failing example using the line number from the failure output.

Example:

$ rspec ./spec/assignment_spec.rb:20

Rerunning Failures

Use --only-failures to rerun only the last failing specs.

First, configure RSpec:

RSpec.configure do |config|
  config.example_status_persistence_file_path = 'spec/examples.txt'
end

Then, rerun failures:

$ rspec --only-failures

Use --next-failure to run the next failure in the list of failed examples.

Focusing Examples

Use fcontext, fit, or fdescribe to temporarily focus on specific examples.

Configure RSpec to run focused examples:

RSpec.configure do |config|
  config.filter_run_when_matching(focus: true)
end

Example:

fcontext 'when a student enrolls' do
  fit 'should be able to enroll a student' do
    # ... test enrollment ...
  end
end

Remember to remove focus tags after use!

Tag Filtering

Use metadata tags to filter examples from the command line.

Example:

$ rspec --tag last_run_status:failed

Marking Work in Progress

Empty Examples

Empty it blocks are marked as pending with asterisks (*).

it 'should have a due date'

Pending Examples

Use the pending method inside a spec to mark it as work in progress.

it 'should have a due date' do
  pending 'Due date not implemented yet'
  expect(assignment.due_date).to be_a(Date)
end

Use skip or xit to completely skip the spec.

Completing Work

RSpec will mark pending examples as failing once they start passing. Remove the pending call to complete the example.


Posted

in

by