How to test a database call when using asynchronous task like GenServer.cast(...)?

Sample Code:

def create_user(attrs) do
 case %User{} |> User.changeset(attrs) |> Repo.insert() do
    {:ok, user} -> 
       EventHandler.user_created(attrs)
       {:ok, user}
    {:error, changeset} -> {:error, changeset}
 end
end

Asynchronous GenServer

def handle_cast({:user_created, attrs}, state) do
  # Logs to event store
  # Saves to another database table
  {:no_reply, state}
end

In test:

attrs = %{name: "John", role_id: 1}
{:ok, user} = Accounts.create_user(attrs)
:timer.sleep(500)
assert AnotherTable.get_user(user.id).role_id == attrs.role_id

It works IF I have a :timer.sleep(500) but it will be very slow if I have similar tests like this. Is there any good alternatives to test async tasks? Thanks guys.

:wave:

You can probably replace :timer.sleep/1 with :sys.get_state/1. It would return after your {:user_created, attrs} cast has been processed.

attrs = %{name: "John", role_id: 1}
{:ok, user} = Accounts.create_user(attrs)
:sys.get_state(pid_or_name_of_your_async_genserver)
assert AnotherTable.get_user(user.id).role_id == attrs.role_id

You can also split the test in two, with only testing handle_cast in one, and create_user in the other.

# test 1
EventHandler.handle_cast({:user_created, attrs})
# asserts
# test 2
{:ok, user} = Accounts.create_user(attrs)
# asserts + you can try to intercept the event handler message here possibly with :erlang.trace
5 Likes

You can also read into the following topic, which also discusses a few tactics to test async operations:

3 Likes