Тестирование асинхронного кода в 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