--breakpoint flag changes the behaviour of mix test using bypass

Background

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 sending messages to callers.

Problem

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

Confusion

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:

  • Press n line by line.
  • When reaching the last line (Manager.login(credentials, false), I do not execute it with n or c. Instead I manually type the command Manager.login(credentials, false).
  • The call to bypass is made
  • Then I press c and the test passes

This is beyond confusing to me. It appears that the test process in not invoking Manager.login like it should.

I cannot explain this.

Questions

  1. Why is the bypass being called when I invoke Manager.login manually using the --breakpoint option and not when mix test runs?
  2. How can I fix my test?

Is it possible that when ran without breakpoints the test finishes before the process makes the call. While with breakpoints, by the time you press c, Manage.login/2 makes its way to send the message?

I’d try to synchronize the test and Bypass with something like:

test "logs in user correctly", %{bypass: bypass, credentials: credentials} do
  test_pid = self()

  Bypass.expect_once(bypass, fn conn ->
    IO.inspect(conn.request_path, label: "REQ PATH")
    IO.inspect(conn.method, label: "METHOD")
  
    send(test_pid, :logging_in)
    
    Plug.Conn.resp(conn, 429, ~s<{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}>)
  end)
  
  _manager_pid = start_link_supervised!(ManagerSupervisor)
  Manager.login(credentials, false)

  # this ensures that the test will not exit until the message is received
  # (or until the default `assert_receive` timeout 100ms)
  assert_receive :logging_in
end
2 Likes

Turns out that was the reason of the failure.
By using assert_receive(:login, 50000) I am able to have bypass receive the call.

The weird thing is that I tried it before, perhaps not with the correct timeout. Thanks for the help!