When using ES/CQRS, events are first-class citizens. It's critical to be able to assert that specific events are being emitted. Commanded provides test helpers to simplify your life.
Please refer to the Testing your application page on the Wiki for help with configuring your test environment.
If you rely on your read projections in your tests, remember to truncate the projection_versions
table in your truncate_readstore_tables/0
function. Otherwise, your projector will ignore everything but the first projection.
defp truncate_readstore_tables do
"""
TRUNCATE TABLE
table1,
table2,
table3,
projection_versions
RESTART IDENTITY
CASCADE;
"""
end
Often you'll want to make sure a given event is published. Commanded provides assert_receive_event/3
and assert_receive_event/4
functions in the Commanded.Assertions.EventAssertions
module to help with this.
In the first case, we check that any event is received and use the argument as an assertion.
In the second case, we also provide a predicate function that we can use to narrow our search down to a specific event.
import Commanded.Assertions.EventAssertions
test "ensure any event of this type is published" do
:ok = MyApp.dispatch(%Command{id: 4, date: Date.today})
assert_receive_event(MyApp, Event, fn event ->
assert event.id == 4
end)
end
test "ensure an event is published matching the given predicate" do
:ok = MyApp.dispatch(%Command{id: 4, date: Date.today})
assert_receive_event(
MyApp,
Event,
fn event -> event.id == 4 end,
fn event ->
assert event.date == Date.today
end
)
end
Use the wait_for_event/2
and wait_for_event/3
functions to pause until a specific type of event, or event type matching a given predicate, is received. This can help you deal with eventual consistency in your tests.
import Commanded.Assertions.EventAssertions
test "pause until specific event is published" do
:ok = BankApp.dispatch(%OpenBankAccount{account_number: "ACC123", initial_balance: 1_000})
wait_for_event(BankApp, BankAccountOpened, fn opened -> opened.account_number == "ACC123" end)
end
It's a given that when going through CQRS, sometimes many events are part of the same action, either because they are returned from the aggregate together, or because event handlers trigger new commands which generate new events, etc. We will usually want to know, for audit trail purposes, that these events belong together.
For this purpose, Commanded provides assert_correlated/4
which can be used to ensure that specific events have the same correlation_id
:
import Commanded.Assertions.EventAssertions
test "make sure two events are correlated" do
:ok = BankApp.dispatch(%OpenBankAccount{account_number: "ACC123", initial_balance: 1_000})
assert_correlated(
BankApp,
BankAccountOpened, fn opened -> opened.account_number == "ACC123" end,
InitialAmountDeposited, fn deposited -> deposited.account_number == "ACC123" end
)
end
Sometimes it's useful to compare an expected aggregate's state with the previous one. This kind of method should be used only for testing.
For this purpose, Commanded provides an aggregate_state
method which returns the current aggregate state.
import Commanded.Assertions.EventAssertions
alias Commanded.Aggregates.Aggregate
test "make sure aggregate state are what we wanted" do
account_number = "ACC123"
:ok = BankApp.dispatch(%OpenBankAccount{account_number: account_number, initial_balance: 1_000})
:ok = BankApp.dispatch(%WithdrawnMoney{account_number: account_number, amount: 200})
wait_for_event(BankApp, BankAccountOpened, fn opened -> opened.account_number == "ACC123" end)
wait_for_event(BankApp, MoneyWithdrawn, fn withdrawn -> withdrawn.balance == 800 end)
assert Aggregate.aggregate_state(BankApp, BankAccount, account_number) == %BankAccount{
account_number: account_number,
balance: 800,
state: :active
}
end