🎓 A1Learner
🔍
0 of 46 lessons completed

Elixir Setup and Fixtures

A setup block is code that runs before a test and hands that test its starting values. Those starting values are called fixtures.

A setup block hands the test its values

The setup block returns a list of numbers. The test reads that list from its context and adds it.

ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule NumbersTest do
  use ExUnit.Case

  setup do
    {:ok, numbers: [10, 20, 30]}
  end

  test "adds the numbers", %{numbers: numbers} do
    assert Enum.sum(numbers) == 60
  end
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")

Shared values and per-test values combine

The setup_all block runs once and shares a base value. The setup block builds a total from it for every test.

ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule BaseTest do
  use ExUnit.Case

  setup_all do
    {:ok, base: 10}
  end

  setup %{base: base} do
    {:ok, total: base + 5}
  end

  test "uses both values", %{total: total} do
    assert total == 15
  end

  test "uses the shared value", %{base: base} do
    assert base == 10
  end
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")

One setup block can hand over several values

The setup block returns a name and an age. Each test reads only the value it needs from the context.

ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule PersonTest do
  use ExUnit.Case

  setup do
    {:ok, name: "Ana", age: 31}
  end

  test "reads the name", %{name: name} do
    assert name == "Ana"
  end

  test "reads the age", %{age: age} do
    assert age == 31
  end
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")

Example

ExUnit.start(seed: 0, auto_run: false, formatters: [])
defmodule ScoresTest do
  use ExUnit.Case

  setup_all do
    {:ok, scores: [4, 5, 6]}
  end

  setup %{scores: scores} do
    {:ok, total: Enum.sum(scores), count: length(scores)}
  end

  test "adds the scores", %{total: total} do
    assert total == 15
  end

  test "counts the scores", %{count: count} do
    assert count == 3
  end

  test "reads the first score", %{scores: scores} do
    assert List.first(scores) == 4
  end
end
result = ExUnit.run()
IO.puts("passed: #{result.total - result.failures}/#{result.total}")

Key points

  • Fixture: a starting value a test needs, such as a list or a count.
  • Setup: a setup block builds the fixture fresh for every test.
  • Context: the map a setup block returns, which the test reads through its second argument.
  • Setup_all: a setup_all block runs once for the whole module and shares its values with every test.
  • Isolation: a fresh fixture per test keeps one test from changing another test's starting values.

In this example, a setup_all block shares a list of scores with every test, and a setup block derives a total and a count from that list for each test. Three tests check the total, the count, and the first score, and all three pass.

Note

setup_all values are shared, so a test must not change them.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

What does a setup block return so the test can read its values?