Skip to content
AgileLogics
5 min read

Testing with RSpec

Prerequisites: Testing with Minitest

RSpec is the other major Ruby testing framework, and the most common one in Rails applications. It describes behavior in a style that reads almost like sentences.

Setup

bundle init
bundle add rspec --group test
bundle exec rspec --init

rspec --init creates a spec/ folder and a .rspec config file. Test files live in spec/ and end in _spec.rb.

The same cart tests in RSpec

# spec/cart_spec.rb
require_relative "../cart"

RSpec.describe Cart do
  subject(:cart) { described_class.new }

  it "starts empty" do
    expect(cart.total).to eq(0)
    expect(cart.items).to be_empty
  end

  describe "#add" do
    it "adds the price to the total" do
      cart.add("book", 12)
      cart.add("pen", 3)

      expect(cart.total).to eq(15)
    end

    context "when the price isn't positive" do
      it "raises an error" do
        expect { cart.add("free", 0) }
          .to raise_error(ArgumentError, "price must be positive")
      end
    end
  end
end
$ bundle exec rspec --format documentation

Cart
  starts empty
  #add
    adds the price to the total
    when the price isn't positive
      raises an error

Finished in 0.00312 seconds (files took 0.0785 seconds to load)
3 examples, 0 failures
  • describe groups tests about one thing; "#add" is the convention for an instance method.
  • context describes a situation ("when ...").
  • it is one example (test).
  • subject / let define lazily-created values, recreated fresh for each example.
  • expect(actual).to matcher — common matchers: eq, be_empty, include, be_nil, raise_error, change { ... }.by(1).

Minitest or RSpec?

Both are mature and capable. Minitest is plain Ruby and ships with the language; RSpec has a richer vocabulary and is standard in many Rails teams. Learn to read both — use whichever the project already uses.

Common mistakes

  • Putting setup code directly inside describe instead of in let or before. Code in describe runs once when the file loads, not before each example.
  • Writing expect(cart.add("free", 0)).to raise_error without the block { }. The error is raised before RSpec can catch it.