test "expressions" do
assert {:ok, true} = Compiler.eval(quote(do: gt(add(4, @age), 20)), %{age: 18})
assert {:ok, 22} = Compiler.eval(quote(do: add(4, @age)), %{age: 18})
end
with this:
test "expressions with macro" do
assert_expr(true, %{age: 18}) do
gt(add(4, @age), 20)
end
assert_expr(22, %{age: 18}) do
add(4, @age)
end
end
But I must be stupid or tired or something, because I tried every possible combinations of quote/unquote I could think of, but I cannot get the macro to work (here is the general idea):
defmacro assert_expr(result, binding, do: block) do
quote do
# result should be matched like ^result if it was in a function, and block should be passed as AST
assert {:ok, result} = Compiler.eval(block, binding)
end
end
Looks like it’s about parametrization of the test with various %{age: ...} maps. Considering that test/3 is the macro by itself, it can be done like the following:
for {age, expected_sum} <- [{18, 22}, {19, 23}] do
@age %{age: age}
test "add #{inspect(@age)}" do
assert add(4, @age) == unquote(expected_sum)
end
test "gt add #{inspect(@age)}" do
assert gt(add(4, @age), 20) == true
end
end
I very much like @IvanR 's solution, and that’s probably the way to go in this particular example.
But to answer the question on how a macro like your assert_expr might be written: you probably need to use Macro.escape here:
defmacro assert_expr(result, binding, do: block) do
quote do
assert {:ok, unquote(result)} = Compiler.eval(unquote(Macro.escape(block)), unquote(binding))
end
end
Or, using bind_quoted:, which is usually more readable than using unquote:
defmacro assert_expr(result, binding, do: block) do
quote bind_quoted: [result: result, binding: Macro.escape(binding), block: Macro.escape(block)] do
assert {:ok, result} = Compiler.eval(block, binding)
end
end