Testing with RSpec

describe, context, it, subject, let (lazy), let! (eager), before/after/around hooks

eq, eql, equal, be, be_nil, be_truthy, include, match, start_with, raise_error, change, output, contain_exactly, match_array, have_attributes, be_within, satisfy, predicate matchers (be_empty → empty?)

double, instance_double (verified), allow(...).to receive(...), expect(...).to receive(...), and_return, and_raise, spies with have_received

shared_examples, it_behaves_like, shared_context :focus, :slow, tags, --tag, pending, skip

.rspec, spec_helper.rb, --format documentation, random order (--order random)

Ecosystem: FactoryBot (test data), Faker, WebMock/VCR (HTTP stubbing/recording), Capybara (browser testing), SimpleCov (coverage), Timecop (time travel, or ActiveSupport's travel_to), mutant (mutation testing), Cucumber (BDD with Gherkin).

# spec/bank_account_spec.rb
require "bank_account"

RSpec.describe BankAccount do
  subject(:account) { described_class.new("Ana", 100) }

  describe "#deposit" do
    it "increases the balance" do
      expect { account.deposit(50) }.to change(account, :balance).by(50)
    end

    context "with a negative amount" do
      it "raises an error" do
        expect { account.deposit(-5) }.to raise_error(ArgumentError, /positive/)
      end
    end
  end

  describe "#withdraw" do
    it "returns false when funds are insufficient" do
      expect(account.withdraw(500)).to be false
      expect(account.balance).to eq(100)
    end
  end

  describe "notifications" do
    let(:notifier) { instance_double("Notifier", notify: true) }

    it "notifies on large deposits" do
      account = described_class.new("Bo", 0, notifier:)
      account.deposit(10_000)
      expect(notifier).to have_received(:notify).with(/large deposit/)
    end
  end

  it "matches complex structures" do
    expect(account.to_h).to include(owner: "Ana", balance: a_value > 0)
    expect([1, 2, 3]).to contain_exactly(3, 1, 2)
    expect("hello").to start_with("he").and end_with("lo")
  end
end