Packages
x3m_system
0.9.1
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
retired
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
retired
0.7.7
0.7.6
retired
0.7.5
0.7.4
retired
0.7.3
retired
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
retired
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.1.1
0.1.0
Building blocks for distributed and/or CQRS/ES systems
Current section
Files
Jump to
Current section
Files
lib/aggregate/test_support.ex
defmodule X3m.System.Aggregate.TestSupport do
@moduledoc """
Helpers for testing aggregates as pure functions, in a given/when/then style.
An aggregate command is a plain function
`cmd(X3m.System.Message.t(), X3m.System.Aggregate.State.t())` returning
`{:block | :noblock, message, state}`. To test one:
* **given** - `state_from_events/3` rebuilds the state from prior events;
* **when** - `command_message/3` builds the message; you call the command;
* **then** - `apply_events/4` folds the events the command emitted onto the given
state so you can assert the resulting `client_state`.
No aggregate process or event store is involved.
"""
alias X3m.System.{Aggregate, Message}
@doc """
Builds a `X3m.System.Message` for `service_name`, with `raw_request` and each entry of
`assigns` applied via `X3m.System.Message.assign/3`.
## Examples
iex> msg = X3m.System.Aggregate.TestSupport.command_message(:open_account, %{"id" => "a1"}, %{invoked_by: %{admin?: true}})
iex> {msg.service_name, msg.raw_request, msg.assigns}
{:open_account, %{"id" => "a1"}, %{invoked_by: %{admin?: true}}}
"""
@spec command_message(service_name :: atom, raw_request :: map, assigns :: map) :: Message.t()
def command_message(service_name, raw_request \\ %{}, assigns \\ %{}) do
service_name
|> Message.new(raw_request: raw_request)
|> _apply_assigns(assigns)
end
@doc """
Folds `events` onto an existing `state` via `aggregate_mod`'s generated `apply_events/3`.
Options:
* `:version` - version to stamp on the result. Defaults to
`state.version + length(events)`.
"""
@spec apply_events(
aggregate_mod :: module,
state :: Aggregate.State.t(),
events :: [any],
opts :: Keyword.t()
) :: Aggregate.State.t()
def apply_events(aggregate_mod, %Aggregate.State{} = state, events, opts \\ []) do
version = Keyword.get(opts, :version, state.version + length(events))
aggregate_mod.apply_events(events, version, state)
end
@doc """
Builds the state you'd have after `events` were applied to `aggregate_mod`'s initial
state - `apply_events/4` starting from `X3m.System.Aggregate.initial_state/1`.
Options:
* `:version` - version to stamp on the result. Defaults to `length(events) - 1`
(initial version `-1` plus one per event), matching a fresh stream.
"""
@spec state_from_events(aggregate_mod :: module, events :: [any], opts :: Keyword.t()) ::
Aggregate.State.t()
def state_from_events(aggregate_mod, events, opts \\ []),
do: apply_events(aggregate_mod, Aggregate.initial_state(aggregate_mod), events, opts)
@spec _apply_assigns(Message.t(), map) :: Message.t()
defp _apply_assigns(message, assigns),
do: Enum.reduce(assigns, message, fn {key, val}, msg -> Message.assign(msg, key, val) end)
end