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
describegroups tests about one thing;"#add"is the convention for an instance method.contextdescribes a situation ("when ...").itis one example (test).subject/letdefine 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
describeinstead of inletorbefore. Code indescriberuns once when the file loads, not before each example. - Writing
expect(cart.add("free", 0)).to raise_errorwithout the block{ }. The error is raised before RSpec can catch it.