Tests async code

Hi,

I’m testing a PhoenixController which triggers background jobs and I don’t know how to test the background job doesn’t fail.

So, I have a Controller which sends a message to a GenServer. The Genserver does something and then, triggers a cast. I just want to make sure the cast finishes properly but I don’t know how to do it.

Here a sample code, reproducing what I’m talking about:

defmodule MyApplication do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec

    opts = [strategy: :one_for_one, name: MyApplication.Supervisor]
    Supervisor.start_link([worker(MyGenServer, [])], opts)
  end
end
defmodule MyGenServer do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, [], [name: __MODULE__])
  end

  def do_something(thing) do
    GenServer.call(__MODULE__, {:do_something, thing})
  end

  def do_something_else(thing) do
    GenServer.cast(__MODULE__, {:do_something_else, thing})
  end

  def handle_call({:do_something, thing}, _from, state) do
    # do all the synchronous stuff
    # ....
    # then trigger some slow work behind the scene
    do_something_else(thing)

    {:reply, :ok, state}
  end

  def handle_cast({:do_something_else, thing}, state) do
    Process.sleep(100)

    raise "Booom from #{thing}"

    {:noreply, state}
  end
end
defmodule MyController do
  def create(params) do
    MyGenServer.do_something(params["form"]["name"])
  end
end
defmodule DummyTest do
  use ExUnit.Case, async: true

  setup_all do
    MyApplication.start(nil, nil)

    :ok
  end

  test "how can I wait for the handle_cast to finish?" do
    assert :ok == MyController.create(%{"form" => %{"name" => "dummy"}})
  end

  test "another test just to make sure mix doesn't stop before the end of the cast function" do
    Process.sleep(1_000)

    assert true
  end
end

The output is

mix test
warning: this check/guard will always yield the same result
  test/dummy_test.exs:70


13:54:15.665 [error] GenServer MyGenServer terminating
** (RuntimeError) Booom from dummy
    test/dummy_test.exs:39: MyGenServer.handle_cast/2
    (stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:667: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", {:do_something_else, "dummy"}}
State: []
..

Finished in 4.0 seconds
2 tests, 0 failures

Randomized with seed 468679

I would like to have 2 tests, 1 failure instead of 2 tests, 0 failure but I don’t know how to do it.

Thanks for your help :slight_smile:

I test such code as suggested in official guides: you can make a monitor and assert whether your GenServer received particular message.
And you can test the logic in your handle_cast separately: one test to assert your GenServer receives required message (with required params) and another one - your code in handle_cast works as you expect with valid message & params.
handle_cast - is just a function in a module (even if this module implements GenServer behaviour).

PS: mb there are some more convenient approaches?)