lib/ex_unit_receiver.ex

defmodule ExUnitReceiver do
  @moduledoc """
  Using a receiver in your test cases.

  A `Receiver` can be used to test higher order functions by using `ExUnitReceiver` in an `ExUnit` test case.
  Consider the following example:

      defmodule Worker do
        def perform_complex_work(val, fun) do
          val
          |> do_some_work()
          |> fun.()
          |> do_other_work()
        end

        def do_some_work(val), do: val |> :math.log() |> round()

        def do_other_work(val), do: val |> :math.exp() |> round()
      end

      defmodule HigherOrderTest do
        use ExUnit.Case
        use ExUnitProperties
        use ExUnitReceiver

        setup do
          start_receiver(fn -> nil end)
          :ok
        end

        def register(x) do
          get_and_update_receiver(fn _ -> {x, x} end)
        end

        property "it does the work in stages with help from an anonymous function" do
          check all int <- positive_integer() do
            result = Worker.perform_complex_work(int, fn x -> register(x) end)
            receiver = get_receiver()

            assert Worker.do_some_work(int) == receiver
            assert Worker.do_other_work(receiver) == result
          end
        end
      end

  When you call the `start_receiver` functions within a `setup` block, it delegates to
  `ExUnit.Callbacks.start_supervised/2`. This will start the receiver as a supervised process under
  the test supervisor, automatically starting and shutting it down between tests to clean up state.
  This can help you test that your higher order functions are executing with the correct arguments
  and returning the expected results.
  """

  defmacro __using__(opts) do
    quote do
      use Receiver, unquote(Keyword.merge(opts, test: true))
    end
  end
end