Fang-Pen Lin
Fang-Pen Lin

Reputation: 14446

How to assert Elixir messages arrive in order

I am building an Elixir module that sends message in certain order. And I wrote unit tests like this

MyModule.send_msgs(self())
assert_received {:event, 1}
assert_received {:event, 2}
assert_received {:event, 3}

The problem is, even if I shuffle the assert_received order, the test still passes.

MyModule.send_msgs(self())
assert_received {:event, 2}
assert_received {:event, 1}
assert_received {:event, 3}

It seems assert_received doesn't care about the order of message arrival. Not just assert_received, as far as I recall, receive can also get messages out of order. So, my question here is, how can you ensure the order of arriving message if they can be received out of order?

Upvotes: 7

Views: 1912

Answers (2)

Khanh Hua
Khanh Hua

Reputation: 1116

I'm quite late but here's what I would do. Within the receiving process, take a look into its mailbox

pid = self()
{:message, messages} = Process.info(pid, :messages)

assert Enum.sort(messages) == messages

Maybe I have missed something but apparently it is quite straight forward.

Upvotes: 1

Dogbert
Dogbert

Reputation: 222448

There doesn't seem to be an existing function to do this, but it's easy to make your own macro which asserts that the next message in the mailbox matches a specific pattern:

defmacro assert_next_receive(pattern, timeout \\ 100) do
  quote do
    receive do
      message ->
        assert unquote(pattern) = message
    after unquote(timeout) ->
      raise "timeout" # you might want to raise a better message here
    end
  end
end

Test:

defmodule MTest do
  use ExUnit.Case

  def send_msgs(pid) do
    send pid, {:event, 1}
    send pid, {:event, 2}
    send pid, {:event, 3}
  end

  defmacro assert_next_receive(pattern, timeout \\ 100) do
    quote do
      receive do
        message ->
          assert unquote(pattern) = message
      after unquote(timeout) ->
        raise "timeout" # you might want to raise a better message here
      end
    end
  end

  test "should pass" do
    send_msgs(self())
    assert_next_receive {:event, 1}
    assert_next_receive {:event, 2}
    assert_next_receive {:event, 3}
  end

  test "should fail" do
    send_msgs(self())
    assert_next_receive {:event, 1}
    assert_next_receive {:event, 3}
    assert_next_receive {:event, 2}
  end
end

Output:

.

  1) test should fail (MTest)
     test/m_test.exs:29
     match (=) failed
     code:  {:event, 3} = message
     right: {:event, 2}
     stacktrace:
       test/m_test.exs:32: (test)



Finished in 0.05 seconds
2 tests, 1 failure

Randomized with seed 351622

Upvotes: 10

Related Questions