I am trying to do an integration test for a simple terminal application that I am writting. This application is basically a Supervisor, which supervises other smaller libraries.
The communication within the project is mostly asynchronous, with processes send
ing messages to callers.
One of the tests I make requires an HTTP request, so I decided to use bypass
for this. However, the problem is rather surprising to me. Here is the test:
setup do
bypass = Bypass.open(port: 8082)
credentials = %{email: "an_email", pass: "a_password"}
%{
credentials: credentials,
bypass: bypass
}
end
test "logs in user correctly", %{bypass: bypass, credentials: credentials} do
Bypass.expect_once(bypass, fn conn ->
IO.inspect(conn.request_path, label: "REQ PATH")
IO.inspect(conn.method, label: "METHOD")
Plug.Conn.resp(conn, 429, ~s<{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}>)
end)
# start the application
_manager_pid = start_link_supervised!(ManagerSupervisor)
# this should make a GET request to bypass but ...
Manager.login(credentials, false)
end
If I simply run mix test
this simple test fails as Bypass received no requests.
1) test login logs in user correctly (Manager.WorkerTest)
test/integration/manager_test.exs:83
No HTTP request arrived at Bypass
However, confunsingly enough, if I use the --breakpoints
feature by running iex -S mix test --breakpoints
I can make the test pass by doing some simple steps:
n
line by line.Manager.login(credentials, false
), I do not execute it with n
or c
. Instead I manually type the command Manager.login(credentials, false)
.bypass
is madec
and the test passesThis is beyond confusing to me. It appears that the test process in not invoking Manager.login
like it should.
I cannot explain this.
bypass
being called when I invoke Manager.login
manually using the --breakpoint
option and not when mix test
runs?After more investigation I realized the test process running the test was finishing too fast. Turns out the delay for http requests to bypass and to other IO tasks the code had to do was taking too long, and so the test would end before the process running the test would receive any messages.
To this effect, even though the suggestion from @Peaceful James of using Process.sleep
would work, I decided for a different approach, by using assert_receive
which does have an option for a timeout, where it forces the test process to wait for a set amount of time, and then shows the processe's entire mailbox if no messages matching the given pattern were received.
The final test would look something like this:
test "logs in user correctly", %{bypass: bypass, credentials: credentials} do
Bypass.expect_once(bypass, fn conn ->
Plug.Conn.resp(conn, 429, ~s<{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}>)
end)
_manager_pid = start_link_supervised!(ManagerSupervisor)
Manager.login(credentials, false)
# 5 secs timeout
assert_receive({:login, :ok}, 5000)
end
Credit to the original solution can be found in this post: