mustela

mustela

Testing async tasks

Hey all!

Im wondering if anyone have a good example or could help me understand how should I test an async task.

The closer I found was this question Testing Task Async, but it’s not that clear (at least for me).

So basically I have a module

defmodule Personas
  def onboard(email) do
    Task.start_link(fn -> process_onboard(email) end)
    init_state()
  end
end

init_state return a struct with some initial state. But the process_onboard function does a bunch of stuff, like reading data from external services and then create some records in the db (basically the persona).

I couldn’t find a way to make sure the task had run before actually testing things, so depending on how fast the task run I randomly get good or bad assertions (all the external services are mock). So I ended up adding a :timer.sleep(200) before checking for instance that the persona was onboarded.

result = Personas.onboard(@email)

# TODO: Find a better way to test async process
# if I dont use the sleep I could get error on the
# assertions. 200 milliseconds is enough to have
# the process completed
:timer.sleep(200)

persona = PersonaQuery.by_email(@email)

assert %{
    first_name: "John",
    last_name: "Doe"
} = persona

I would love to get rid of that sleep, so I would appreciate any thoughts/ideas around it.

Thanks a lot!

Marked As Solved

stefanchrobot

stefanchrobot

Have a look here.

Also Liked

josevalim

josevalim

Creator of Elixir

Unless you are using async/await, then the recommendation is to supervisor your tasks. Supervising your tasks will allow you to control shutdown, measure how many tasks are running, and so on. It also comes with the benefit of making the system easier to test.

mustela

mustela

Hey everyone! First of all, I have to thank you all for your awesome feedback. The fact that even @josevalim is answering to this “simple” question, expose how humble and awesome this community is… :bowing_man:

About my particular problem, I took more time reading about supervisors and processes (Im kind of new in elixir) and it def makes more sense to use them. My brain is still trying to use other languages patterns, and not the specifics of the Elixir language. Lesson learned!

Again, thank you all for taking the time to try to help with my issue! :heart:

thiagomajesk

thiagomajesk

Hi @mustela!

I agree with @LostKobrakai’s response from the topic you linked: Testing Task Async - #2 by LostKobrakai. I would refactor the code to unit test only the abstractions that I have control over and by definition can properly be observed.

I’d say that if both functions inside onboard/1 don’t depend on each other, you should probably be unit-testing them separately. On the other hand, if they do and you’re not using await to guarantee the task has run, you’ll probably have to deal with some sort of race condition.

So the question in my head is if you should be using async at this level of the abstraction. Perhaps you should make the code inside process_onboard/1 async, but the process_onboard/1 function itself returns something like {:ok | :error}.

For example, this would be easier to test:

def process_onboard(email) do
  task1 = do_some_stuff()
  task2 = do_some_more_stuff()
  task3 = do_even_more_stuff()

 Task.await_many([task1, task2, task3])

  :ok
end

def do_some_stuff(), do: :ok
def do_some_more_stuff(), do: :ok
def do_even_more_stuff(), do: :ok
defmodule Personas
  def onboard(email) do
    with :ok <- process_onboard(email), do: init_state()
  end
end

I’m quoting this because when we are talking about code that it’s difficult to test we can most of the time correlate with abstraction problems.

Update: I want to link this Kent Beck post because I feel this is relevant to the discussion. Hope that this helps you find out which abstractions of your application should be tested.

stefanchrobot

stefanchrobot

Makes sense, but the testing “difficulty” kind of exposes the shortcomings of the code. Is it possible for the task to crash or timeout? You’re linking the task to whatever process is starting it, which means that if it goes down it will potentially take down the other process too. Managing the error conditions is the primary reason why no task should run unsupervised.

Where Next?

Popular in Questions Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New

Other popular topics Top

skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement