Skip to content

Instantly share code, notes, and snippets.

@amos-kibet
Created June 8, 2024 08:16
Show Gist options
  • Save amos-kibet/12d1788a3d172102b18722a651834804 to your computer and use it in GitHub Desktop.
Save amos-kibet/12d1788a3d172102b18722a651834804 to your computer and use it in GitHub Desktop.
Testing async Elixir code
## Method 1 (from Dockyard's [blog post](https://dockyard.com/blog/2024/06/06/a-better-solution-for-waiting-for-async-tasks-in-tests))
Create a helper module, with this function:
```Elixir
def flush_task_supervisor do
pids = Task.Supervisor.children(MyApp.TaskSupervisor)
for pid <- pids do
ref = Process.monitor(pid)
assert_receive {:DOWN, ^ref, _, _, _}
end
end
```
Then, in your test, call the function, after your code execution, but before your assertions.
```Elixir
test "does something async" do
MyApp.do_something_async()
flush_task_supervisor()
assert MyApp.check_something()
end
```
## Method 2 (the one I ue all the time)
First, start a Task supervisor in all environments (dev, stage, etc), except in test environment. Only start the supervisor in the specific tests that need it.
```Elixir
# *my_app/lib/my_app/application.ex*
def start(type, args) do
children = [_other_children] ++ more_children()
end
defp more_children(env \\ Application.get_env(:my_app, :env))
defp more_children(:test), do: []
defp more_children(_env), do: [{Task.Supervisor, name: MyApp.TaskSupervisor}]
```
Then, in your specific tests that has async code, add this to your test setup:
```Elixir
setup do
start_supervised!({Task.Supervisor, name: MyApp.TaskSupervisor, strategy: :one_for_one})
:ok
end
```
and write your tests normally. The setup will ensure that before your test exits, the async Task process will have returned.
@amos-kibet
Copy link
Author

Method 1 (from Dockyard's blog post)

Create a helper module, with this function:

def flush_task_supervisor do
  pids = Task.Supervisor.children(MyApp.TaskSupervisor)

  for pid <- pids do
    ref = Process.monitor(pid)
    assert_receive {:DOWN, ^ref, _, _, _}
  end
end

Then, in your test, call the function, after your code execution, but before your assertions.

test "does something async" do
  MyApp.do_something_async()

  flush_task_supervisor()

  assert MyApp.check_something()
end

Method 2 (the one I ue all the time)

First, start a Task supervisor in all environments (dev, stage, etc), except in test environment. Only start the supervisor in the specific tests that need it.

# *my_app/lib/my_app/application.ex*

def start(type, args) do
    children = [_other_children] ++ more_children()
end

defp more_children(env \\ Application.get_env(:my_app, :env))
defp more_children(:test), do: []
defp more_children(_env), do: [{Task.Supervisor, name: MyApp.TaskSupervisor}]

Then, in your specific tests that has async code, add this to your test setup:

setup do
  start_supervised!({Task.Supervisor, name: MyApp.TaskSupervisor, strategy: :one_for_one})

  :ok
end

and write your tests normally. The setup will ensure that before your test exits, the async Task process will have returned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment