defmodule CustomerPromotionHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: __MODULE__
def handle(%CustomerPromoted{} = event, _metadata) do
%CustomerPromoted{customer_id: customer_id} = event
Notification.send_promotion(customer_id)
end
end
Following the unit testing matrix outlined by Sandi Metz in her talk The Magic Tricks of Testing), to test side affects in an event handler you could use a mock to verify the expected external command is called. You don’t test the actual side effects when unit testing the handler, but you do verify they are called.
You can either call the event handler’s handle/2 function directly or cause the event to be produced (via command dispatch or appending the event to a stream) and afterwards verify the side-affect was called.
defmodule CustomerPromotionTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
test "`send_promotion/1` is called by promotion handler" do
reply_to = self()
NotificationMock.expect(:send_promotion, customer_id ->
send(reply_to, {:send_promotion, customer_id})
:ok
end)
# Start handler process and dispatch command to produce trigger event
start_supervised!(CustomerPromotionHandler)
:ok = MyApp.dispatch(command)
assert_receive {:send_promotion, ^expected_customer_id}
# Or call handler directly with an event (doesn’t require the handler process to be started)
:ok = CustomerPromotionHandler.handle(event, metadata)
end
end
With Mox you must define a behaviour which can then be mocked or stubbed at runtime.
defmodule NotificationBehaviour do
@callback send_promotion(non_neg_integer()) :: :ok | {:error, any()}
end
It's often useful to define a facade module for the behaviour which can substitute the mock at runtime or compile time. In this example the implementation module (real or mock) is determined at compile time from application config:
defmodule Notification do
@behaviour NotificationBehaviour
@implementation Application.get_env(:myapp, __MODULE__, Notification.DefaultImpl)
defdelegate send_promotion(customer_id), to: @implementation
end
defmodule Notification.DefaultImpl do
@behaviour NotificationBehaviour
def send_promotion(customer_id) do
# Actual implementation goes here ...
end
end
With the above setup the Notification.DefaultImpl module containing the actual implementation will be used in non-test environments and the mock will be used during tests. This allows the side effect call (send_promotion/1) to be tested, without actually causing the side effect. It also allows you to easily test failures by returning a different response from the mock call (e.g. {:error, failed_to_send}).
To test the event handler and its side effects you could use an integration style test and would replace the side effect module mock with the actual implementation. You could use Mox’s stub_with(NotificationMock, Notification.DefaultImpl) which forwards all calls to the mock to the actual implementation. This ensures the contract is valid between the caller and the target module.
How to test event handler side effects.
Following the unit testing matrix outlined by Sandi Metz in her talk The Magic Tricks of Testing), to test side affects in an event handler you could use a mock to verify the expected external command is called. You don’t test the actual side effects when unit testing the handler, but you do verify they are called.
You can either call the event handler’s
handle/2
function directly or cause the event to be produced (via command dispatch or appending the event to a stream) and afterwards verify the side-affect was called.With Mox you must define a behaviour which can then be mocked or stubbed at runtime.
It's often useful to define a facade module for the behaviour which can substitute the mock at runtime or compile time. In this example the implementation module (real or mock) is determined at compile time from application config:
The mock must be defined in the test support:
Then configured for the test environment:
With the above setup the
Notification.DefaultImpl
module containing the actual implementation will be used in non-test environments and the mock will be used during tests. This allows the side effect call (send_promotion/1
) to be tested, without actually causing the side effect. It also allows you to easily test failures by returning a different response from the mock call (e.g.{:error, failed_to_send}
).To test the event handler and its side effects you could use an integration style test and would replace the side effect module mock with the actual implementation. You could use Mox’s
stub_with(NotificationMock, Notification.DefaultImpl)
which forwards all calls to the mock to the actual implementation. This ensures the contract is valid between the caller and the target module.