Ruby's dynamic nature makes tests the main safety net, and the community treats them as non-negotiable.
Ruby has two dominant test frameworks.
Minitest ships with Ruby (as a bundled gem) and is what Rails uses by default. Tests are classes inheriting from Minitest::Test, and every method starting with test_ is a test:
assert_equal expected, actual,assert,refute,assert_nil,assert_includes,assert_in_deltafor floats.assert_raises(KeyError) { ... }returns the exception so you can check its message.setupandteardownrun around each test.
Run a file with ruby -Itest test/cart_test.rb, or through Rake or Rails. Minitest also has a spec-style syntax (describe/it) and simple mocks and stubs (Object#stub from minitest/mock).
RSpec is the other big choice, with a descriptive DSL:
RSpec.describe Cart do
it "sums prices" do
expect(Cart.new([2, 3]).total).to eq(5)
end
endIt offers rich matchers (eq, include, raise_error, change { }.by(1)), let for lazily defined fixtures, before hooks, shared examples, and built-in doubles (instance_double(Mailer, deliver: true)), which verify that stubbed methods really exist.
Whichever you pick: test behaviour through public methods, keep each test independent, prefer dependency injection over global stubbing, and run the suite in random order (both do by default) to expose hidden coupling.
Example
# test_cart.rb: run with `ruby test_cart.rb`
require "minitest/autorun"
class Cart
def initialize(prices) = @prices = prices
def total = @prices.sum
def item(i) = @prices.fetch(i)
end
class CartTest < Minitest::Test
def setup
@cart = Cart.new([2, 3.5])
end
def test_total
assert_in_delta 5.5, @cart.total
end
def test_missing_item_raises
err = assert_raises(IndexError) { @cart.item(9) }
assert_match(/index 9/, err.message)
end
end
# 2 runs, 4 assertions, 0 failures, 0 errors, 0 skipsRemember
- Minitest is built in: subclass Minitest::Test and write test_ methods
- RSpec uses describe/it/expect with rich matchers and verified doubles
- Tests run in random order, so keep them independent
Watch out for
- Putting the actual value first in assert_equal, which makes failure messages misleading.