Application_module - Better mocking ergonomics

Hey folks :wave:

I just shipped a new Elixir package to improve the ergonomics using mocking libraries like Mox or Hammox, application_module. I talked about the motivations in detail in this blog post, but I’ll leave you here with the TL;DR;

  • Mox and Hammox propose to have a runtime function that reads the module from the application environment.
  • This is common across all the modules whose tests will want to mocks some of their dependencies. This leads to a lot of duplications and potentially inconsistencies in how that information is structured inside the application environment.
  • There might also be inconsistencies arising from how developers organize the behaviours and their default implementations.

I decided to eliminate all the duplications and embrace a more conventional approach enforced via macros. Here’s an example that illustrates the before and after using the package:

Before

defmodule MyModule do
  @behaviour __MODULE__.Behaviour

  def hello(name) do
    application_env_module().hello(name)
  end

  def application_env_module() do
    get_in(Application.get_env(:my_app, :modules), [:my_module]) || __MODULE__.Implementation
  end

  defmodule Implementation do
    @behaviour MyModule.Behaviour

    def hello(name) do
      "Hello #{name}"
    end
  end

  defmodule Behaviour do
    @callback hello(name :: String.t()) :: any()
  end
end

After

defmodule MyModule do
  use Application.Module

  defimplementation do
    def hello(name) do
      "Hello #{name}"
    end
  end

  defbehaviour do
    @callback hello(name :: String.t()) :: any()
  end
end

Any feedback is truly appreciated. It was my first time implementing macros myself so I went through a bunch of hiccups and back-and-forths.