TL;DR
I found an interesting bug, not in the implementation code, but in the ExUnit test. The bug was related to Kernel.match?
method. I will explain the root cause of this bug and how you should use match
method along your ExUnit asserts.
Kernel.match?/2
This is a convenience macro that checks if the right side (an expression) matches the left side (a pattern). As the Kernel module is available in Elixir by default, it is enough to just write match?
It is important to refresh the Elixir variables pattern matching feature.
iex(2)> match?({:ok, expected}, {:ok, 5})
warning: variable “expected” is unused (if the variable is not meant to be used, prefix it with an underscore)
iex:2true
In order for your pattern matching understanding, explain to your rubber duck why we have a warning and true
as a result.
ExUnit
Here is a simple ExUnit script with the false-positive result when we use match?
iex(1)> import ExUnit.Assertions
ExUnit.Assertions
iex(2)> alias ExUnit.Assertions
ExUnit.Assertions
iex(3)> assert 2
2
iex(4)> assert 2 == 2
true
iex(5)> expected = {:ok, 10}
{:ok, 10}
iex(6)> assert match?(expected, {:ok, 8 + 3})
warning: variable “expected” is unused (there is a variable with the same name in the context, use the pin operator (^) to match on it or prefix this variable with underscore if it is not meant to be used)
iex:6true
Root Cause
In order to find out the root cause, the question is why match?(expected, {:ok, 8 + 3})
returned true, while expected value is no match for right side value. The reason is expected
variable. Remember that with pattern matching you can also assign values to variables. And in that case, as a side effect, we have a pattern match.
Solution
In order to use match?
, one option is not to use variables, or a better option is to use the pin ^
operator.
iex(11)> assert match?({:ok, 10}, {:ok, 8 + 3})
** (ExUnit.AssertionError)match (match?) failed
code: assert match?({:ok, 10}, {:ok, 8 + 3})
left: {:ok, 10}
right: {:ok, 11}(ex_unit 1.12.3) lib/ex_unit/assertions.ex:401: ExUnit.Assertions.assert/2
A better option with pin ^
operator:
iex(1)> expected = {:ok, 10}
{:ok, 10}
iex(2)> import ExUnit.Assertions
ExUnit.Assertions
iex(3)> alias ExUnit.Assertions
ExUnit.Assertions
iex(4)> assert match?(^expected, {:ok, 8 + 3})
** (ExUnit.AssertionError)match (match?) failed
The following variables were pinned:
expected = {:ok, 10}
code: assert match?(^expected, {:ok, 8 + 3})
left: ^expected
right: {:ok, 11}(ex_unit 1.12.3) lib/ex_unit/assertions.ex:401: ExUnit.Assertions.assert/2
Comments are closed.