
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.