Test helpers for asserting on emitted signals, and for testing listeners.
assert_emits_signal/4 and refute_emits_signal/4 check the signals emitted while running a
function, and need no setup:
import Ash.Signals.Test
test "creating a post emits post_created" do
post =
assert_emits_signal MyApp.Blog.Signals, :post_created, %{title: "Hello"}, fn ->
MyApp.Blog.create_post!("Hello")
end
assert post.title == "Hello"
endassert_signal_emitted/4 and refute_signal_emitted/4 check the signals emitted so far, and
require calling capture_signals/1 first:
setup :capture_signals
test "creating a post emits post_created" do
%{id: post_id} = MyApp.Blog.create_post!("Hello")
assert_signal_emitted MyApp.Blog.Signals, :post_created, %{post_id: ^post_id}
endOnly signals emitted by the test process, or by processes it started (that have it in their
$callers, like tasks and LiveViews under test), are seen. No other process is sent anything,
and listeners still run as usual.
Patterns match against the signal struct, and may use pins or bind variables.
To test listeners on their own, react_to_signal/4 delivers a signal to them as if it had been
emitted:
test "placing an order awards points" do
:ok =
react_to_signal(
MyApp.Shop.Signals,
:order_placed,
%{order_id: order.id, customer_id: customer.id},
only: MyApp.Loyalty.Account
)
assert MyApp.Loyalty.points!(customer) == 100
endTo also match on who emitted a signal, receive the messages sent after capture_signals/1
directly. They have the shape {:ash_signal_emitted, signal_module, signal_name, signal, metadata}, where metadata is the metadata of the [:ash, :signal, :emitted] telemetry
event, see Ash.Signals.
Summary
Functions
Runs the function, asserts that it emitted a signal matching the given pattern, and returns the function's result. Variables bound by the pattern are available afterwards.
Asserts that a signal matching the given pattern was emitted since capture_signals/1 was
called, and returns the signal.
Sends signals emitted by the current test, or by processes it started, to the test process,
for assert_signal_emitted/4 and refute_signal_emitted/4.
Delivers a signal to its listeners as if it had been emitted with the given payload, so listeners can be tested without the action that emits it. Pass a list of payloads to deliver signals emitted together, like a bulk action does.
Runs the function, asserts that it did not emit a signal matching the given pattern, and returns the function's result.
Asserts that no signal matching the given pattern was emitted since capture_signals/1 was
called.
Functions
Runs the function, asserts that it emitted a signal matching the given pattern, and returns the function's result. Variables bound by the pattern are available afterwards.
assert_emits_signal MyApp.Blog.Signals, :post_created, fn ->
MyApp.Blog.create_post!("Hello")
end
post =
assert_emits_signal MyApp.Blog.Signals, :post_created, %{post_id: post_id}, fn ->
MyApp.Blog.create_post!("Hello")
end
assert post.id == post_id
Asserts that a signal matching the given pattern was emitted since capture_signals/1 was
called, and returns the signal.
assert_signal_emitted MyApp.Blog.Signals, :post_created
assert_signal_emitted MyApp.Blog.Signals, :post_created, %{title: "Hello"}
%{post_id: post_id} = assert_signal_emitted MyApp.Blog.Signals, :post_createdThe optional timeout, defaulting to 0, waits for signals emitted by other processes.
@spec capture_signals(map()) :: :ok
Sends signals emitted by the current test, or by processes it started, to the test process,
for assert_signal_emitted/4 and refute_signal_emitted/4.
Can be used as setup :capture_signals. Stops when the test exits.
@spec react_to_signal( Ash.Signals.t(), atom(), map() | Keyword.t() | [map()], Keyword.t() ) :: :ok | {:error, term()}
Delivers a signal to its listeners as if it had been emitted with the given payload, so listeners can be tested without the action that emits it. Pass a list of payloads to deliver signals emitted together, like a bulk action does.
Each listener receives the signal through its own delivery, as it would if the signal had been emitted, so a listener that runs in a job only enqueues it. Nothing is emitted, so the signal isn't seen by the assertions above.
Returns :ok, or the error of the first delivery that fails.
Options
:only- Only delivers to the listeners on these resources, or to these{resource, listener_name}pairs. Raises if no listener matches.:inline?- Runs every listener immediately, whatever its delivery. Defaults tofalse.:actor,:tenant,:authorize?,:tracer,:contextand:scope- What listeners run with, as forAsh.Signals.emit/5.
react_to_signal(
MyApp.Shop.Signals,
:order_placed,
%{order_id: order_id, customer_id: customer_id},
only: {MyApp.Loyalty.Account, :award_points},
actor: customer
)
Runs the function, asserts that it did not emit a signal matching the given pattern, and returns the function's result.
refute_emits_signal MyApp.Blog.Signals, :post_created, fn ->
MyApp.Blog.create_draft!("Hello")
end
Asserts that no signal matching the given pattern was emitted since capture_signals/1 was
called.
refute_signal_emitted MyApp.Blog.Signals, :post_created
refute_signal_emitted MyApp.Blog.Signals, :post_created, %{title: "Draft"}The optional timeout, defaulting to 0, waits for signals emitted by other processes.