Can you pin a temporary variable while asserting a pattern match

I’m trying to do a relatively simple pattern match

test "pattern matching assert" do
  user = Repo.get(User, 1)
  assert %{id: ^user.id} = user
end

This is obviously a simplified case. I would actually like to pattern match multiple keys (but ignore many more) of a JSON response. This could be fixed by creating a user_id variable:

test "pattern matching assert" do
  user = Repo.get(User, 1)
  user_id = user.id
  assert %{id: ^user_id} = user
end

But if there’s 6 different keys I would like to test for I wouldn’t want to create temporary variables for each of them just to assert their value when something like:

assert %{id: ^(user.id)} = user

or

assert %{id: pin(user.id)} = user

Where pin/1 is some special macro. Would capture the intent very clearly without extra noise and un-needed variables. This doesn’t seem like an outlandish question but I’ve been unable to find anything useful via searching.

Edit: fixed a typo that may have been obscuring some of my intent. The code used to read: assert %{id: ^(user.id) = user} instead of assert %{id: ^(user.id)} = user

1 Like

I don’t think you can use function calls in matches at all.

I have a habit of writing an assert_equivalent method I use in my crud tests instead of matching all keys on one line

def assert_equivalent(expected, response) do
    assert expected.title == response.title
    assert_equivalent_slug(expected.slug, response.slug)
    assert expected.info == response.info
end

You can pin in an assert just fine.

If you want a pin function though, well you could use a macro:

defmacro pin(expr) do
  quote do ^unquote(expr) end
end

@OvermindDL1 this code refuses to compile

defmacro pin(expr) do
  quote do ^unquote(expr) end
end

test "pin variable" do
  user = %{id: 5}
  assert %{id: pin(user.id)} = %{id: 5}
end

With: invalid argument for unary operator ^, expected an existing variable, got: ^user.id()

@law Yeah, that approach works, I’d just like something a little lighter-weight and easier to use if possible.

It won’t work anywhere that a normal pin would not work. :wink:

user.id is a function call, specifically it is doing Map.fetch(user, :id), which you cannot pin.

You should probably just destructure it in the prior call:

defmacro pin(expr) do
  quote do ^unquote(expr) end
end

test "pin variable" do
  %{id: id} = user = %{id: 5}
  assert %{id: pin(id)} = %{id: 5}
end

You could even assert the destructuring line too.

1 Like