Testing Events
Event testing covers two things separately: that a service emits the right events, and that a handler invokes the right services when an event fires. Testing them independently means you can refactor either side with clear feedback about what changed.
Testing that a service emits events
Use the emit_event matcher to assert a service emits an event on success or failure:
ruby
RSpec.describe Treasury::TransferGold::Service do
let(:from_account) { create(:account, balance: 1000) }
let(:to_account) { create(:account, balance: 500) }
it "emits gold_transferred on success" do
expect {
described_class.call(
from_account: from_account,
to_account: to_account,
gold_dragons: 50
)
}.to emit_event(:gold_transferred)
end
it "emits with the transfer amount in the payload" do
expect {
described_class.call(
from_account: from_account,
to_account: to_account,
gold_dragons: 50
)
}.to emit_event(:gold_transferred).with(hash_including(transferred: 50))
end
endYou can also pass a handler class instead of a symbol:
ruby
expect {
described_class.call(
from_account: from_account,
to_account: to_account,
gold_dragons: 50
)
}.to emit_event(GoldTransferredHandler)Testing event handlers
Handlers are tested by calling their handle class method with a payload. Use the call_service matcher to assert which services are invoked:
ruby
RSpec.describe GoldTransferredHandler do
let(:payload) do
{
transferred: 50,
from_balance: 950,
to_balance: 550
}
end
it "invokes Ledger::RecordEntry" do
expect {
described_class.handle(payload)
}.to call_service(Ledger::RecordEntry::Service)
end
it "invokes it asynchronously" do
expect {
described_class.handle(payload)
}.to call_service(Ledger::RecordEntry::Service).async
end
it "invokes it with the expected arguments" do
expect {
described_class.handle(payload)
}.to call_service(Ledger::RecordEntry::Service).with(
transferred: payload[:transferred]
)
end
endChaining
.async and .with can be combined:
ruby
expect {
described_class.handle(payload)
}.to call_service(Ravens::SendReceipt::Service)
.with(amount: 50)
.asyncTesting conditional invocations
When a handler uses if: or unless: conditions, test both paths:
ruby
RSpec.describe GoldTransferredHandler do
context "when transfer exceeds 100 gold dragons" do
let(:payload) { { transferred: 150, from_balance: 850, to_balance: 650 } }
it "dispatches a raven" do
expect {
described_class.handle(payload)
}.to call_service(Ravens::DispatchMessage::Service).async
end
end
context "when transfer is 100 or less" do
let(:payload) { { transferred: 50, from_balance: 950, to_balance: 550 } }
it "does not dispatch a raven" do
expect {
described_class.handle(payload)
}.not_to call_service(Ravens::DispatchMessage::Service)
end
end
end