When writing tests in Elixir using ExUnit, you may want to run the same test logic with multiple input values—much like data providers in PHPUnit. While ExUnit doesn’t have a built-in equivalent, you can achieve the same result by dynamically generating test cases using for or Enum.each.

In this post, we’ll look at how to implement parameterized tests and why unquote is essential when doing so inside macros.

Basic parameterized test with Enum.each

You can group multiple test cases into a single test using Enum.each:

test "adds numbers correctly" do
  [
    {1, 2, 3},
    {5, 5, 10},
    {-1, 1, 0}
  ]
  |> Enum.each(fn {a, b, expected} ->
    assert a + b == expected
  end)
end

This works well, but all cases are part of the same test. If one fails, the whole test fails.

generating separate tests with for

To create separate test cases (so that each one is reported independently by the test runner), you can use a for comprehension with the test macro:

for {a, b, expected} <- [
      {1, 2, 3},
      {5, 5, 10},
      {-1, 1, 0}
    ] do
  test "adding #{a} + #{b} gives #{expected}" do
    assert unquote(a) + unquote(b) == unquote(expected)
  end
end

This pattern creates multiple independent tests, one for each set of values.

Why unquote Is Required

Elixir macros work by manipulating quoted code (abstract syntax trees). When you use the test macro inside a loop, you’re generating code at compile time, not runtime. That means the variables a, b, and expected in the body of the test are part of a quoted block.

To insert the actual values from the loop into the generated test, you need to use unquote. Without it, the values would remain as variable references and wouldn’t be interpolated properly, leading to incorrect or failing tests.

For example, the line:

assert unquote(a) + unquote(b) == unquote(expected)

inserts the concrete values from the loop directly into the generated test, resulting in:

test "adding 1 + 2 gives 3" do
  assert 1 + 2 == 3
end

This ensures the test behaves as expected.

Conclusion

While ExUnit doesn’t have built-in data providers like PHPUnit, Elixir’s powerful macro system and list comprehension features make it easy to write parameterized tests. Just remember to use unquote to inject loop variables into your dynamically generated test code.

This approach keeps your tests concise, expressive, and fully integrated with ExUnit’s reporting and tooling.