Rspec Cheatsheet

10 Apr 2025 - Gagan Shrestha

Mastering RSpec: A Comprehensive Guide to Testing in Ruby

RSpec is one of the most popular testing frameworks in the Ruby ecosystem, providing an expressive and powerful syntax for writing tests. This guide covers essential RSpec features including expectations, matchers, and test doubles to help you write effective and readable tests.

Basic Expectations

RSpec offers two syntaxes for writing expectations. The older should syntax and the newer expect syntax:

Should Syntax (Legacy)

1
2
target.should eq 1
target.should_not eq 1
1
2
expect(target).to eq 1
expect(target).not_to eq 1

The expect syntax is now the recommended approach as it avoids extending core Ruby objects and provides more consistent behavior.

Object Expectations

Testing object types and capabilities:

1
2
3
4
5
6
7
8
# Verify exact class
expect(obj).to be_an_instance_of MyClass

# Verify inheritance hierarchy
expect(obj).to be_a_kind_of MyClass

# Verify method existence
expect(obj).to respond_to :save!

Numeric Expectations

RSpec provides numerous matchers for numeric comparisons:

1
2
3
4
5
6
7
8
9
10
# Basic comparisons
expect(5).to be < 6
expect(5).to == 5
expect(5).to equal value

# Range verification
expect(5).to be_between(1, 10)

# Delta comparison (floating point)
expect(5).to be_within(0.05).of value

Control Flow Expectations

Testing for exceptions and thrown symbols:

1
2
3
4
5
6
7
8
# Test for any exception
expect { user.save! }.to raise_error

# Test for specific exception and message
expect { user.save! }.to raise_error(ExceptionName, /msg/)

# Test for thrown symbols (less common)
expect { user.save! }.to throw :symbol

Compound Expectations

Combining expectations with logical operators:

1
2
# Combining expectations
expect(1).to (be < 2).or be > 5

Collection Expectations

Testing arrays and other enumerable objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Test for inclusion
expect(list).to include(<object>)

# Test collection size
expect(list).to have(1).things
expect(list).to have_at_least(2).things
expect(list).to have_at_most(3).things

# Test validation errors
expect(list).to have(2).errors_on(:field)

# Test exact contents (order-independent)
expect(list).to contain_exactly(1, 2)
expect(list).to match_array([1, 2])

Comparison Expectations

General-purpose comparison matchers:

1
2
3
4
5
6
7
8
# Identity comparison
expect(x).to be value

# Custom verification
expect(x).to satisfy { |arg| ... }

# Regular expression matching
expect(x).to match /regexp/

Predicate Expectations

RSpec automatically converts Ruby predicate methods into expectations:

1
2
3
expect(x).to be_zero    # Uses FixNum#zero?
expect(x).to be_empty   # Uses Array#empty?
expect(x).to have_key   # Uses Hash#has_key?

Test Doubles

Test doubles (also known as mocks) allow you to create stand-ins for real objects in your tests:

Creating Doubles

1
2
3
4
5
# Basic double
book = double('book')

# Instance double (class-verified)
book = instance_double(Book, pages: 250)

Method Stubs

Setting up return values for methods:

1
2
3
4
5
6
7
8
# Basic stubbing
allow(die).to receive(:roll)

# Stubbing with return value
allow(die).to receive(:roll) { 3 }

# Stubbing any instance of a class
allow_any_instance_of(Die).to receive(:roll)

Method Expectations

Verifying that methods are called with specific arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Basic expectation
expect(die).to receive(:roll)

# Argument matching
expect(die).to receive(:roll)
  .with(1)                       # Exact match
  .with(1, true)                 # Multiple arguments
  .with(boolean)                 # Type matching
  .with(anything)                # Any object
  .with(any_args)                # Any arguments
  .with(1, any_args)             # First arg specific, rest anything
  .with(no_args)                 # No arguments
  .with(hash_including(a: 1))    # Hash with specific keys
  .with(hash_excluding(a: 1))    # Hash without specific keys
  .with(array_including(:a, :b)) # Array with specific elements
  .with(array_excluding(:a, :b)) # Array without specific elements
  .with(instance_of(Fixnum))     # Instance of exact class
  .with(kind_of(Numeric))        # Instance of class or subclass
  .with(<matcher>)               # Any custom matcher

Call Count Expectations

Verifying how many times a method is called:

1
2
3
4
5
6
7
8
9
10
expect(die).to receive(:roll)
  .once                      # Called exactly once
  .twice                     # Called exactly twice
  .exactly(n).times          # Called exactly n times
  .at_least(:once)           # Called at least once
  .at_least(:twice)          # Called at least twice
  .at_least(n).times         # Called at least n times
  .at_most(:once)            # Called at most once
  .at_most(:twice)           # Called at most twice
  .at_most(n).times          # Called at most n times

Best Practices

  1. Prefer the expect syntax over should
  2. Use descriptive context and example names
  3. Follow the “arrange, act, assert” pattern
  4. Test one behavior per example
  5. Use let for shared test data
  6. Consider using subject for the object under test
  7. Use before blocks sparingly
  8. Test the public interface, not implementation details

Conclusion

RSpec provides a rich vocabulary for expressing test expectations in Ruby. By mastering these matchers and techniques, you can write tests that are both thorough and readable, serving as living documentation for your codebase.