How to default ExUnit.CaseTemplate to `async: true`

I’ve been using Elixir for a while and only today realized that the default async value for use ExUnit.Case is false. :man_facepalming:

It seems I’m not the only one who thought this was the case: Default value for ExUnit.Case async

Anyways, I spent some time looking at the code of ExUnit.Case as well as ExUnit.CaseTemplate, and was able to figure out a way to make async: true the default. So I thought I’d share it here:

defmodule MyApp.Case do
  @moduledoc false

  use ExUnit.CaseTemplate

  # Override `ExUnit.CaseTemplate.__using__` so we can manipulate opts and
  # default to `async: true`.
  # For reference, see the original `defmacro __using__` that is injected
  # by ExUnit.CaseTemplate:
  defmacro __using__(opts) do
    parent = ExUnit.CaseTemplate.__proxy__(__MODULE__, default_async_true(opts))
    injected_frontmatter = frontmatter()
    {:__block__, [], [parent, injected_frontmatter]}

  defp default_async_true(opts) do
    case Keyword.fetch(opts, :async) do
      {:ok, false} ->
        # If `async: false` is specified, leave opts alone.

      _ ->
        # If `async: false` is not specified, set `async: true`
        Keyword.put(opts, :async, true)

  defp frontmatter do
    quote do
      # Here's where you'd put useful aliases that normally go in `using do`

Now if you do use MyApp.Case, it’ll get async: true by default!

I’m not knocking your solution or anything but if you use Credo, its default is to force you to always provide the :async option be it true or false. As mentioned in your linked thread, the default is false for a good reason and it’s good to be intentional about whether a test needs to be synchronous or not. Manually specifying it in all tests removes any question of whether or not its omission was accidental.

EDIT: hmmm, I’m seeing this is a two month old post. It was clearly in my “suggest topics” but I read it as a new one. Sorry.

I run Credo and I have never been enforced to apply this rule.

Ah, you are correct, it’s not enabled by default. It does ship with Credo but is in the disabled section by default.

1 Like

I didn’t know such a feature existed. Thanks for mentioning it. I think I will start using it.

1 Like