I have a simple GenServer that have a few functions (write, delete and update records in ETS). All functions is async (GenServer.cast). Now I am trying to find a right way to testing that module.
In my simple test i have something like
setup_all do
MyServer.save({"a", 1})
end
test "test save item" do
item = MyServer.lookup("a")
assert length(item) == 1
end
test "test delete item" do
MyServer.delete("a")
delItem = MyServer.lookup("a")
assert delItem == []
end
First (save test) works fine because setup_all executed before test execution start.
But with second test I have a problem. Due to async calls, delItem = MyServer.lookup(“a”) finished before record really deleted from ETS and test fails.
Any idea how to test similar functionality will be very helpful.
Not sure if this is best approach but I’ve added a synchronous method to gensever (called handle_call(:sync, ...) and I call it from test. Because elixir process does one thing at a time and messages are ordered, I know that if such sync call finishes, the async casts before had to run already.
if Mix.env() == :test do
def handle_call(....), do: ....
end
If you don’t need it in other envs.
Another option that sometimes might be needed is to just have an helper function that executes a given fun and loops until X time has passed or a condition is achieved.
def wait_until(fun, timeout \\ 500, time \\ 0)
def wait_until(fun, timeout, time) when time > timeout, do: false
def wait_until(fun, timeout , time) do
case fun.() do
is_ok when is_ok in [true, :ok] or is_tuple(is_ok) and elem(0, is_ok) == :ok -> true
_ ->
Process.sleep(1)
wait_until(fun, timeout, time + 1)
end
end
And then use it as:
assert Support.wait_until(
fn() ->
case MyServer.lookup("a") do
[] -> :ok
_ -> false
end
end,
200
)