Written September 20, 2015. Tagged Elixir, ExUnit, Testing.
Say you have this code:
defmodule Example do
def run(callback) do
callback.(:hello, :world)
do_more_stuff
end
end
You want to assert that it calls back with :hello
and :world
.
It might not be immediately clear how to do that in ExUnit.
test "callback runs" do
callback = fn (greeting, celestial_body) ->
# ?
end
Example.run(callback)
#?
end
We could assert inside the callback… but if the callback never runs, the assertion won't run either.
In a language like Ruby, you could do it by changing a variable outside the anonymous function:
did_it_run = false
fun = -> { did_it_run = true }
fun.()
assert did_it_run
In Elixir, an anonymous function can read variables from outside but not change them. We could start a separate server process and make it hang on to this state, but that would be a bit of a bother.
There are other ways to communicate, though. Message passing to the rescue!
test "callback runs" do
callback = fn (greeting, celestial_body) ->
send self, {:called_back, greeting, celestial_body}
end
Example.run(callback)
assert_received {:called_back, :hello, :world}
end
We simply send
a message to our own process from the callback. Now it's in our process mailbox.
Then we assert that we received it.
For multi-process use cases, you can name the test process:
defmodule TestCallerBacker do
def run(greeting, celestial_body) do
send :test, {:called_back, greeting, celestial_body}
end
end
test "callback runs" do
Process.register self, :test
Example.run_in_another_process(TestCallerBacker)
assert_received {:called_back, :hello, :world}
end
assert_received
expects the message to have arrived already. If your code is asynchronous and the message may take a while to arrive, its companion function assert_receive
lets you specify a timeout.