Monday, October 26, 2015

Testing messages with Elixir

If you're coming to Elixir from another language, you might wonder how to assert that a process sent a message to another process.

The key is to remember that each test itself is also an Erlang process (and has its own pid).

We can send a message to the test process's mailbox, and then use `assert_receive` to assert we got the message.

For example:

test "something" do
  IO.puts "I am test, my pid is #{inspect self}"
  send(self, "hello test")

  assert_receive "hello test"

When you want to assert that a process sends a message, you just need to make sure the process is sending the messages back to the test's pid.

We do this by creating a stub that forwards messages.


Say you have GenServer process that handles Payroll. Each time an employee is registered with the process, it sends a notification using GenEvent.
You want to assert that the event is sent.

This is what your Payroll server looks like:

defmodule Payroll do
  use GenServer

  # convenience function for starting up server, requires an event manager
  def start_link(manager) do
    # start GenServer with current module, and pass manager as state
    GenServer.start_link(__MODULE__, [%{events: manager}])

  # initialize state
  def init([state]) do
    {:ok, state}

  def handle_call({:add, employee}, _from, state) do
    # send out event
    GenEvent.notify(, {:added, employee})

    # reply :ok
    {:reply, :ok, state}

  # public API for adding employee
  def add_employee(pid, employee) do, {:add, employee})

It can be used like this:

# start a gen event server
{:ok, events} = GenEvent.start

# start the payroll service and pass the event manager's pid
{:ok, payroll} = Payroll.start_link(events)

# spawn another process that will print out events
spawn fn ->
    |> Stream.each(&IO.inspect/1)
    |> Enum.to_list

# add an employee to the payroll service, which will result in notification being sent to previously spawned process
Payroll.add_employee(payroll, %{name: "Josh", salary: 1_000_000})

To test it, we'll need to create a stub GenEvent handler to forward messages back to the test:

# inside payroll_test.exs

# define a stub GenEvent handler
defmodule Forwarder do
  use GenEvent

  # handle all events, first parameter is the event, second if the state
  def handle_event(event, test_pid) do
    # send the event to `test_pid`
    send(test_pid, event)

    # respond `:ok` and keep the same state (the test's pid)
    {:ok, test_pid}

# create all the processes we need in the setup
# ExUnit automatically terminates child processes after each test completes
setup do
  # create a GenEvent process
  {:ok, events} = GenEvent.start

  # start the payroll process, passing the event process's pid
  {:ok, payroll} = Payroll.start_link(events)

  # add the Forwarder as a handler, pass the current pid (self) as the state
  GenEvent.add_handler(events, Forwarder, self)

  # return test state
  {:ok, %{events: events, payroll: payroll}}

test "adding employee, sends notification", state do
  Payroll.add_employee(state.payroll, %{name: "Josh", salary: 1_000_000})

  assert_receive {:added, %{name: "Josh", salary: 1_000_000}}

And that is all folks..

Happy Elixiring!