ExUnit.LetLazy

For those coming from RSpec and missing let.

Examples

Order does not matter

In the following example, note the way it's not important what order you call let in. The value you provide is a macro expression evaluated when it is used via get.

defmodule AddTest do
use ExUnit.Case, async: true
use ExUnit.LetLazy
# code under test
defp add(a, b) do
a + b
end
describe "add/2 with a = b, b = 15" do
# order is not important, thanks Elixir macros
let :a, get(:b)
let :b, 15
test "gives the correct answer" do
assert add(get(:a), get(:b)) == 30
end
end
end

This is implemented using ETS.

Each test is a mashup of bits of context

Like in RSpec, you can use let to keep the LOC in your test files manageable. Consider this example. Instead of repeating the setup each time with slight variations, all the possible options for arguments are placed at the top, and inside each describe block, we pull in the combination that we want to test.

What's more, we can re-use the exact same context easily in order to test different qualities of our function - e.g. one test to make assertions about the messages it sends, a separate test to make assertions about its return value.

defmodule ReuseTest do
use ExUnit.Case, async: true
use ExUnit.LetLazy
let :a_case_1, %{large_data_structure_1a: "x"}
let :a_case_2, %{large_data_structure_2a: "x"}
let :b_case_1, %{large_data_structure_1b: "x"}
let :b_case_2, %{large_data_structure_2b: "x"}
let :call, my_function(get(:a), get(:b))
# code under test
defp my_function(a, a) do
send self(), {:log, :error, :same}
a
end
defp my_function(a, b) do
Map.merge(a, b)
end
describe "my_function, a = b" do
let :a, get(:a_case_1)
let :b, get(:a)
test "gives the correct answer" do
assert get(:call) == %{large_data_structure_1a: "x"}
end
test "sends the right message" do
get(:call)
assert_received {:log, :error, :same}
end
end
describe "my_function, a != b" do
let :a, get(:a_case_1)
let :b, get(:b_case_1)
test "does not send the message" do
get(:call)
refute_received {:log, :error, _}
end
end
describe "my_function, a case 1, b case 1" do
let :a, get(:a_case_1)
let :b, get(:b_case_1)
test "gives the correct answer" do
assert get(:call) == %{large_data_structure_1a: "x", large_data_structure_1b: "x"}
end
end
describe "my_function, a case 1, b case 2" do
let :a, get(:a_case_1)
let :b, get(:b_case_2)
test "gives the correct answer" do
assert get(:call) == %{large_data_structure_1a: "x", large_data_structure_2b: "x"}
end
end
# ... other combinations of a, b, and assertion ...
end

Installation

Add ex_unit_let_lazy to your list of dependencies in mix.exs:

def deps do
[
{:ex_unit_let_lazy, "~> 0.1.0"}
]
end

The docs can be found at https://hexdocs.pm/ex_unit_let_lazy.

See also

See also ExUnit.Let which is similar, but simpler, uses the test context structure, and thus relies on the ordering of let calls.