在 Elixir 中测试异步代码

问题描述 投票:0回答:3

我想测试一个使用

Task.async

的函数

为了使我的测试通过,我需要在断言之前让它休眠 100 毫秒,否则测试进程会在执行异步任务之前被终止。

有更好的方法吗?

编辑,添加代码示例:

我想测试的代码(大致):

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
elixir
3个回答
42
投票

由于问题有点模糊,我在这里给出一般性答案。通常的技术是监视进程并等待 down 消息。像这样的东西:

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

一些重要的事情:

  • 元组的第五个元素是退出原因。我断言任务出口是

    :normal
    。如果您期望再次退出,请相应地更改它。

  • assert_receive
    中的第二个值是超时。考虑到您当前的睡眠时间为 100 毫秒,500 毫秒听起来是一个合理的量。


10
投票

当我无法使用涉及

assert_receive
的José方法时,我使用一个小助手反复进行断言/睡眠,直到断言通过或最终超时。

这是辅助模块

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

0
投票

Chris 的回答很有帮助,但如果断言失败,stracktrace 将不会指向测试代码的正确行:

wait_until fn ->  # <--- Stacktrace will always point here, no matter which assertion failed
  assert called TStore.store("some tweet from my fixtures")
  assert called TStore.store("another one")
end

如果将 Chris 的解决方案转换为宏,那么您将在堆栈跟踪中获得适当的行号:

defmacro wait_until(do: block) do
  quote do
    wait_until(500, fn -> unquote(block) end) 
  end
end

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

假设您在

DataCase
模块中定义了这个宏(它不必去那里),那么用法将如下所示:

use MyApp.DataCase

wait_until do
  assert Repo.one(MyModel) # Test something async
end
© www.soinside.com 2019 - 2024. All rights reserved.