Skip to content

Commit 7953da4

Browse files
authored
Accept {module, function} tuples in ExUnit setup (#12378)
1 parent 4cb4c96 commit 7953da4

File tree

2 files changed

+64
-21
lines changed

2 files changed

+64
-21
lines changed

lib/ex_unit/lib/ex_unit/callbacks.ex

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ defmodule ExUnit.Callbacks do
1717
test is run. All `setup` callbacks are run before each test. No callback
1818
is run if the test case has no tests or all tests have been filtered out.
1919
20-
`setup` and `setup_all` callbacks can be defined by a block, by passing
21-
an atom naming a one-arity function, or by passing a list of such
22-
atoms. Both can opt to receive the current context by specifying it
20+
`setup` and `setup_all` callbacks can be defined by either a block, an atom
21+
naming a local function, a `{module, function}` tuple, or a list of atoms/tuples.
22+
23+
Both can opt to receive the current context by specifying it
2324
as parameter if defined by a block. Functions used to define a test
2425
setup must accept the context as single argument.
2526
@@ -124,13 +125,13 @@ defmodule ExUnit.Callbacks do
124125
125126
It is also common to define your setup as a series of functions,
126127
which are put together by calling `setup` or `setup_all` with a
127-
list of atoms. Each of these functions receive the context and can
128+
list of function names. Each of these functions receive the context and can
128129
return any of the values allowed in `setup` blocks:
129130
130131
defmodule ExampleContextTest do
131132
use ExUnit.Case
132133
133-
setup [:step1, :step2, :step3]
134+
setup [:step1, :step2, :step3, {OtherModule, :step4}]
134135
135136
defp step1(_context), do: [step_one: true]
136137
defp step2(_context), do: {:ok, step_two: true} # return values with shape of {:ok, keyword() | map()} allowed
@@ -183,8 +184,8 @@ defmodule ExUnit.Callbacks do
183184
@doc """
184185
Defines a callback to be run before each test in a case.
185186
186-
Accepts a block or the name of a one-arity function in the form of an atom,
187-
or a list of such atoms.
187+
Accepts a block, an atom naming a local function, a `{module, function}` tuple,
188+
or a list of atoms/tuples.
188189
189190
Can return values to be merged into the context, to set up the state for
190191
tests. For more details, see the "Context" section shown above.
@@ -218,8 +219,8 @@ defmodule ExUnit.Callbacks do
218219
@doc """
219220
Defines a callback to be run before each test in a case.
220221
221-
Accepts a block or the name of a one-arity function in the form of an atom,
222-
or a list of such atoms.
222+
Accepts a block, an atom naming a local function, a `{module, function}` tuple,
223+
or a list of atoms/tuples.
223224
224225
Can return values to be merged into the `context`, to set up the state for
225226
tests. For more details, see the "Context" section shown above.
@@ -245,7 +246,12 @@ defmodule ExUnit.Callbacks do
245246
@doc false
246247
def __setup__(module, callbacks) do
247248
setup = Module.get_attribute(module, :ex_unit_setup)
248-
Module.put_attribute(module, :ex_unit_setup, Enum.reverse(callbacks(callbacks), setup))
249+
250+
Module.put_attribute(
251+
module,
252+
:ex_unit_setup,
253+
Enum.reverse(validate_callbacks!(callbacks), setup)
254+
)
249255
end
250256

251257
@doc false
@@ -265,8 +271,8 @@ defmodule ExUnit.Callbacks do
265271
@doc """
266272
Defines a callback to be run before all tests in a case.
267273
268-
Accepts a block or the name of a one-arity function in the form of an atom,
269-
or a list of such atoms.
274+
Accepts a block, an atom naming a local function, a {module, function} tuple,
275+
or a list of atoms/tuples.
270276
271277
Can return values to be merged into the `context`, to set up the state for
272278
tests. For more details, see the "Context" section shown above.
@@ -365,7 +371,7 @@ defmodule ExUnit.Callbacks do
365371
Module.put_attribute(
366372
module,
367373
:ex_unit_setup_all,
368-
Enum.reverse(callbacks(callbacks), setup_all)
374+
Enum.reverse(validate_callbacks!(callbacks), setup_all)
369375
)
370376
end
371377

@@ -385,15 +391,21 @@ defmodule ExUnit.Callbacks do
385391
end
386392
end
387393

388-
defp callbacks(callbacks) do
394+
defp validate_callbacks!(callbacks) do
389395
for k <- List.wrap(callbacks) do
390-
if not is_atom(k) do
391-
raise ArgumentError,
392-
"setup/setup_all expect a callback name as an atom or " <>
393-
"a list of callback names, got: #{inspect(k)}"
396+
case k do
397+
{mod, fun} when is_atom(mod) and is_atom(fun) ->
398+
{mod, fun}
399+
400+
name when is_atom(name) ->
401+
name
402+
403+
invalid ->
404+
raise ArgumentError,
405+
"setup/setup_all expect a callback as an atom, " <>
406+
"a {module, function} tuple or a list of callbacks, got: " <>
407+
inspect(invalid)
394408
end
395-
396-
k
397409
end
398410
end
399411

@@ -801,9 +813,19 @@ defmodule ExUnit.Callbacks do
801813
end)
802814
end
803815

804-
defp compile_setup_call(callback) do
816+
defp compile_setup_call(callback) when is_atom(callback) do
805817
quote do
806818
unquote(__MODULE__).__merge__(__MODULE__, var!(context), unquote(callback)(var!(context)))
807819
end
808820
end
821+
822+
defp compile_setup_call({module, callback}) do
823+
quote do
824+
unquote(__MODULE__).__merge__(
825+
__MODULE__,
826+
var!(context),
827+
unquote(module).unquote(callback)(var!(context))
828+
)
829+
end
830+
end
809831
end

lib/ex_unit/test/ex_unit/callbacks_test.exs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ defmodule ExUnit.CallbacksTest do
6565
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures"
6666
end
6767

68+
test "named callbacks support {module, function} tuples" do
69+
defmodule NamedCallbacksTupleTest do
70+
use ExUnit.Case
71+
72+
setup_all {__MODULE__, :setup_1}
73+
setup [{__MODULE__, :setup_2}, {__MODULE__, :setup_3}]
74+
75+
test "callbacks", context do
76+
assert context[:setup_1]
77+
assert context[:setup_2]
78+
assert context[:setup_3]
79+
end
80+
81+
def setup_1(_), do: [setup_1: true]
82+
def setup_2(_), do: [setup_2: true]
83+
def setup_3(_), do: [setup_3: true]
84+
end
85+
86+
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures"
87+
end
88+
6889
test "doesn't choke on setup errors" do
6990
defmodule SetupTest do
7091
use ExUnit.Case

0 commit comments

Comments
 (0)