# Testing with RSpec and Minitest — Ruby

Source: https://www.geekswithgeeks.com/en/ruby/e-testing

> Write tests with RSpec and Minitest, including doubles.

## A strong testing culture

Ruby has a strong testing culture, and two frameworks dominate. **Minitest** ships with Ruby and is used by Rails by default: test classes subclass `Minitest::Test`, methods start with `test_`, and assertions such as `assert_equal(expected, actual)` and `assert_raises` check behaviour; it also offers a spec style. **RSpec** provides a readable DSL: `describe` and `context` group examples, `it` defines them, **`expect(actual).to eq(expected)`** asserts with matchers (`be_truthy`, `include`, `raise_error`, `change { ... }.by(1)`, `have_attributes`), and `let` defines lazily evaluated helpers. Both support **test doubles**: RSpec's `instance_double(PaymentGateway, charge: "pay_1")` creates **verified doubles** that fail if you stub methods the real class does not have, and `expect(gateway).to receive(:charge).with(...)` checks interactions. Use **FactoryBot** for test data, **WebMock** or **VCR** to stub HTTP, and **SimpleCov** for coverage. Run tests on every change in CI, and keep them fast by isolating I/O.

## The same behaviour tested with RSpec and Minitest

Verified doubles catch stubs of methods that do not exist.

```ruby
# spec/checkout_spec.rb (RSpec)
RSpec.describe Checkout do
  let(:gateway) { instance_double(PaymentGateway, charge: "pay_1") }
  subject(:checkout) { described_class.new(gateway) }

  it "charges the order total once" do
    expect(checkout.pay(order_id: "o-1", amount_paise: 49_900)).to eq("pay_1")
    expect(gateway).to have_received(:charge).with("o-1", 49_900).once
  end

  it "rejects non-positive amounts" do
    expect { checkout.pay(order_id: "o-2", amount_paise: 0) }.to raise_error(ArgumentError)
  end
end

# test/checkout_test.rb (Minitest)
require "minitest/autorun"

class CheckoutTest < Minitest::Test
  FakeGateway = Struct.new(:calls) do
    def charge(order_id, amount) = (calls << [order_id, amount]; "pay_1")
  end

  def test_charges_once
    gateway = FakeGateway.new([])
    assert_equal "pay_1", Checkout.new(gateway).pay(order_id: "o-1", amount_paise: 49_900)
    assert_equal [["o-1", 49_900]], gateway.calls
  end
end
```

## Two languages, one meaning

RSpec and Minitest are like writing the same rule in two styles: one as a sentence ("it charges the order once"), the other as a plain checklist. Both prove the same thing; teams choose the style they read best.

**Quiz:** What does RSpec's instance_double provide over a plain double?

- [ ] Faster tests
- [ ] Database access
- [ ] Automatic test data
- [x] Verification that stubbed methods actually exist on the real class with matching arguments

*Answer:* Verification that stubbed methods actually exist on the real class with matching arguments. Verified doubles fail when you stub methods the real class does not define.
