"Your test suite is a time machine—if you built it right."
Traditional testing in Rails?
- Fixtures: Stale, brittle snapshots of fake data.
- Mocks: Lie about how the system behaves.
- Factories: Slow, complex setups.
Event sourcing flips the script. Instead of faking state, replay real history.
Here’s how to test without losing your sanity.
1. The Power of Event-Based Testing
Traditional Testing (CRUD)
# Setup
user = create(:user, balance: 100)
# Test
post :withdraw, params: { amount: 50 }
expect(user.reload.balance).to eq(50)
Problems:
- Requires database.
- Tests state, not behavior.
Event-Sourced Testing
# Setup
events = [
Events::UserRegistered.new(user_id: "u1"),
Events::BalanceDeposited.new(user_id: "u1", amount: 100)
]
# Test
command = Commands::Withdraw.new(user_id: "u1", amount: 50)
new_events = command.execute
expect(new_events).to include(
an_instance_of(Events::BalanceWithdrawn)
.with(amount: 50)
)
Wins:
- No database needed.
- Tests business rules, not persistence.
2. Testing Strategies
Unit Tests: Commands & Aggregates
Test decisions, not side effects:
it "rejects overdrafts" do
events = [Events::BalanceDeposited.new(amount: 100)]
account = Account.new(events)
expect {
account.withdraw(200)
}.to raise_error(Account::InsufficientFunds)
end
Integration Tests: Event Handlers
Verify projections and side effects:
it "upgrades user on 3rd deposit" do
events = 2.times.map { Events::Deposited.new }
handler = Handlers::Loyalty.new
new_events = handler.process(events)
expect(new_events).to include(Events::UserUpgraded)
end
End-to-End: Replay Real Scenarios
Test entire workflows from production data (sanitized):
it "replays checkout flow" do
events = EventStore.load(stream_id: "prod_order_123")
projection = OrderProjection.new(events)
expect(projection.status).to eq(:fulfilled)
end
3. Tools to Make It Easier
- RailsEventStore: Built-in test helpers.
- Eventide: Message-based testing.
- Cucumber: Human-readable replay specs.
4. When to Avoid This Approach
❌ Simple CRUD: Overkill for basic forms.
❌ Legacy systems: Requires event-sourcing adoption first.
"But We Have 1000 Tests Relying on Fixtures!"
Start small:
- Add event publishing to one critical feature.
- Keep old tests, but write new ones against events.
- Gradually migrate as you refactor.
Have you tried event-based testing? Share your wins (or horror stories) below.
Top comments (0)