Testing simple GenServer that write (async) to ETS

Hello,

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.

Thank you!

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.

Interested how others approach this!

2 Likes

Thanks a lot! Very interesting solution and I think that I can use it. Much better than Process.sleep :slight_smile:

You can also wrap that function in a

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
)

1 Like

FWIW, you can use system messages for this too - for instance, :sys.get_state() which all GenServers support.

1 Like