Cannot find or invoke local XYZ inside match -- ideas?

I am trying to make ergonomic test helpers like so:

  def violated_unique_constraint?(
        field,
        {:error,
         %Changeset{
           errors: [
             {field,
              {_,
               [
                 {:constraint, :unique},
                 {:constraint_name, _}
               ]}}
           ]
         }}
      ),
      do: true

  def violated_unique_constraint?(_, _), do: false

Which in my tests I am invoking like so:

assert violated_unique_constraint?(:country_code) =
             insert(%{country_code: "us", ...})

Which fails with:

** (CompileError) test/schema/whatever_test.exs:15 cannot find or invoke local violated_unique_constraint?/1 inside match. Only macros can be invoked in a match and they must be defined before their invocation. Called as: violated_unique_constraint?(:country_code)

You’ll notice I am matching the first parameter inside the second which AFAIK is allowed in Elixir and worked really well for me in other scenarios.

What am I doing wrong?

1 Like

violated_unique_constraint? has arity 2, so

assert violated_unique_constraint?(:country_code, insert(%{country_code: "us", ...}))
4 Likes

That’s what happens when I am in a rush. Thanks for ending my pain. :heart:

    assert violated_unique_constraint?(
             :country_code,
             insert(%{country_code: "us", ...})
           )

The error message is not about arity though, only about the fact that you cannot call a function on the left side of =, though it may work with macros.

violated_unique_constraint?(:country_code) = insert(%{country_code: "us", ...})

Actually you can. It returns true or false by doing pattern-matching on its parameters, so you basically end up with:

assert true|false = another_function_call()

yeah I am trying to test that from the REPL right now ^^

It does not work for me:

iex(1)> defmodule X do
...(1)> def indentity(x), do: x
...(1)> end
{:module, X,
 <<70, 79, 82, 49, 0, 0, 4, 212, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 137,
   0, 0, 0, 14, 8, 69, 108, 105, 120, 105, 114, 46, 88, 8, 95, 95, 105, 110,
   102, 111, 95, 95, 10, 97, 116, 116, 114, ...>>, {:indentity, 1}}
iex(2)> require ExUnit.Assertions
ExUnit.Assertions
iex(3)> ExUnit.Assertions.assert X.identity(1) = 1
** (CompileError) iex:3: cannot invoke remote function X.identity/1 inside a match
    (stdlib 3.14) lists.erl:1358: :lists.mapfoldl/3
    (stdlib 3.14) lists.erl:1358: :lists.mapfoldl/3
    expanding macro: ExUnit.Assertions.assert/1

To be more precise, I know you can assert with a valid match expression, but that’s it. No left-side function calls (same rules as for case, receive).

1 Like

I can only assume this is related to you not actually running that in a full-blown ExUnit tests. Have you tried running your code there?

I ran mix new dimi and then wrote this:

defmodule Dimi do

  def hello do
    :world
  end

  def indentity(x), do: x
end

and this

defmodule DimiTest do
  use ExUnit.Case
  doctest Dimi

  test "greets the world" do
    assert Dimi.hello() == :world
    assert Dimi.identity(1) = 1
  end
end

And you get the same error. Left-side function calls on match operations are not supported. And I don’t see a reason they should be supported in assert since they would not be anywhere else anyway.

Edit: also it makes sense because you can do that:

assert {:ok, data} = stuff()
do_something_with(data)

So the match has to be executed since we may want to work with the bound variables. So it has to be a valid match expression.

1 Like