Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assert Elixir messages arrive in order

Tags:

elixir

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?

like image 294
Fang-Pen Lin Avatar asked Jan 09 '17 08:01

Fang-Pen Lin


1 Answers

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
like image 154
Dogbert Avatar answered Oct 07 '22 00:10

Dogbert