Тестирование асинхронного кода в Elixir

Я хочу проверить функцию, которая использует Task.async

Чтобы пройти мой тест, мне нужно заставить его спать на 100 мс перед утверждениями, иначе тестовый процесс будет убит до того, как будет выполнена асинхронная задача.

Есть ли лучший способ?

EDITED, добавив образцы кода:

Код, который я хотел бы проверить (примерно):

def search(params) do
  RateLimiter.rate_limit(fn ->
    parsed_params = ExTwitter.Parser.parse_request_params(params)
    json = ExTwitter.API.Base.request(:get, "1.1/search/tweets.json", parsed_params)
    Task.async(fn -> process_search_output(json) end)
    new_max_id(json)
  end)
end

И тест, который я уже написал (работает только с вызовом спать)

test "processes and store tweets" do
  with_mock ExTwitter.API.Base, [request: fn(_,_,_) -> json_fixture end] do
    with_mock TwitterRateLimiter, [rate_limit: fn(fun) -> fun.() end] do
      TSearch.search([q: "my query"])
      :timer.sleep(100)
      # assertions 
      assert called TStore.store("some tweet from my fixtures")
      assert called TStore.store("another one")
    end
  end
end

Ответы

Ответ 1

Поскольку вопрос немного расплывчатый, я дам общий ответ здесь. Обычный способ - следить за процессом и ждать сообщения вниз. Что-то вроде этого:

task = Task.async(fn -> "foo" end)
ref  = Process.monitor(task.pid)
assert_receive {:DOWN, ^ref, :process, _, :normal}, 500

Некоторые важные вещи:

  • Пятый элемент кортежа - причина выхода. Я утверждаю, что выход Task имеет значение :normal. Измените это соответственно, если вы ожидаете другого выхода.

  • Второе значение в assert_receive - это таймаут. 500 миллисекунд звучит как разумная сумма, учитывая, что у вас есть сон 100 мс.

Ответ 2

Когда я не могу использовать подход José с участием assert_receive, я использую небольшой помощник для повторного выполнения assertion/sleep, пока утверждение не пройдет или, наконец, не закончится.

Вот вспомогательный модуль

defmodule TimeHelper do

  def wait_until(fun), do: wait_until(500, fun)

  def wait_until(0, fun), do: fun.()

  def wait_until(timeout, fun) defo
    try do
      fun.()
    rescue
      ExUnit.AssertionError ->
        :timer.sleep(10)
        wait_until(max(0, timeout - 10), fun)
    end
  end

end

Его можно использовать как в предыдущем примере:

TSearch.search([q: "my query"])
wait_until fn ->
  assert called TStore.store("some tweet from my fixtures")
  assert called TStore.store("another one")
end