Issues with mocking

Hey all!

I’m trying to use Mock and I’m failing to assert a function is called. When I’m running in development, I know the code behaves correctly, as I can see the calls flying around through the logs.

The context is a chat application, where we send a push notification whenever there’s new content. The general architecture is 3 modules interacting together: RoomChannel is the web socket handler. RoomChannel uses Repo to check authorizations (stored in the database). RoomChannel then uses PushNotifier to actually send the push notification.

First, here’s the failing test and it’s failure:

test "publish_message immediately sends a push notification to unconnected people for picture messages" do
  with_mocks([
    {Repo, [], [
      is_authorized_to_publish_message_to_room: fn(_, _) -> true end,
    ]},
    {PushNotifier, [], [send_push_notification: fn(_, _, _, _) -> true end]},
  ]) do
    {:ok, _room} = open_room "room-name"
    sock = open_socket()
          |> subscribe_and_join!(RoomChannel, "room:room-name")

    push sock, "publish_message", %{
      "id" => "00",
      "room_id" => "room-name",
      "from_nick" => "Matthew S.",
      "body" => %{
        "picture" => %{ "url" => "https://images.example.com/..."
        }
      }
    }

    assert called PushNotifier.send_push_notification("room-name", "USERID", "Matthew S.", "Sent a picture")
  end
end

The failure is:

1) test publish_message immediately sends a push notification to unconnected people for picture messages (BusChatWeb.RoomChannelTest)
   test/channels/room_channel_test.exs:202
   Expected truthy, got false
   code: called(PushNotifier.send_push_notification("room-name", "USERID", "Matthew S.", "Sent a picture"))
   stacktrace:
     test/channels/room_channel_test.exs:229: (test)

The RoomChannel code:

defp publish_message(message, socket) do
  authorized = Repo.is_authorized_to_publish_message_to_room(socket.assigns[:user_id], message["room_id"])
  if authorized do
    if is_payload_broadcastable?(payload), do: broadcast(socket, "publish_message", message)

    case message do
      %{"from_nick" => nick, "body" => %{"text" => text}} ->
        PushNotifier.send_push_notification(message["room_id"], socket.assigns[:user_id], nick, text)

      %{"from_nick" => nick, "body" => %{"picture" => _}} ->
        PushNotifier.send_push_notification(message["room_id"], socket.assigns[:user_id], nick, "Sent a picture")

      _ ->
        # ignore this message for push notification purposes
        # the value here is required by Elixir's syntax, and is meaningless
        0
    end

    {:reply, {:ok, %{payload: payload}}, socket}
  else
    {:reply, {:error, %{reason: :unauthorized}}, socket}
  end
end

By doing logger debugging, I know the 2nd branch of the case statement above was taken, and PushNotifier.send_push_notification/4 was called.

What I did try was adding a call to Logger in the Mock defined in the test. That one didn’t show up when running the test.

I have a very similar test that asserts that a push notification with the message’s body text is sent, and that test passes. The RoomChannelTest uses async: false, so that shouldn’t be it. I also use many mocks elsewhere in RoomChannel, and none of the other tests fail.

I’m kind of at a loss as to where I should be looking to further debug my issue. I would appreciate pointers on what I may have done wrong, or how I could better architect my test to reduce the need for mocks. Obviously, I don’t want to send push notifications from the test itself, so I will have to mock something at some point.

Thanks!
François

Well, I’ll have to clarify: on CI, the sibling test for text messages fails as well. When I ran the tests in a loop, I managed to fail the text only messages locally as well.

I still can’t explain the issue :frowning: