Mox test failing with `stub_with`

,

Background

I have a small app with a pool manager, a couple workers and a registry.
For the sake of learning Mox, I made a small test that uses Mox, but it fails for a reason I can’t understand.

Code

This code is a MWE app that starts a registry and a pool:

defmodule MoxIssue.Application do
  use Application

  def start(_type, _args) do
    Supervisor.start_link(
      [
        MoxIssue.ExRegistry,
        MoxIssue.Pool
      ],
      strategy: :one_for_one
    )
  end
end

The pool creates 3 workers and acts as a supervisor:

defmodule MoxIssue.Pool do

  alias MoxIssue.Worker

  require Logger

  @registry Application.fetch_env!(:mox_issue, :registry)

  def start_link do
    children = Enum.map(1..3, &worker_spec/1)

    Logger.debug("Starting supervisor")

    res = Supervisor.start_link(
      children,
      strategy: :one_for_one,
      name: @registry.via_tuple({__MODULE__, __MODULE__})
    )

    Logger.debug("Supervisor started")
    res
  end

  defp worker_spec(worker_id), do:
    Supervisor.child_spec({Worker, {worker_id}}, id: {worker_id})

  def child_spec(_) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, []},
      type: :supervisor
    }
  end

end

Each Worker is a dummy echo worker that saves a list of messages echoed:

defmodule MoxIssue.Worker do
  use GenServer

  require Logger

  @registry Application.fetch_env!(:mox_issue, :registry)

  defmodule State do
    defstruct messages: [], worker_id: nil
  end

  def start_link({worker_id}) do
    Logger.debug("Starting worker #{inspect worker_id}")

    res = GenServer.start_link(
      __MODULE__,
      %State{worker_id: worker_id},
      name: @registry.via_tuple({__MODULE__, worker_id})
    )

    Logger.debug("Worker registers OK")
    res
  end

  def echo(msg, id) do
    [{pid, _val}] = @registry.lookup({__MODULE__, id})
    GenServer.call(pid, {:echo, msg})
  end

  def init(state), do: {:ok, state}

  def handle_call({:echo, msg}, _from, state) do
    Logger.info(msg)
    new_state = %State{state | messages: [msg] ++ state.messages}
    {:reply, :ok, new_state}
  end
end

Nothing extraordinary here. The most complex piece of code is the Registry, which I took from Elixir in Action and slightly modified for this example. Because Mox really wants you to have contracts, here is the contract used and it’s implementation:

defmodule MoxIssue.ProcessRegistry do
  @type via_tuple_type  ::
    {:via, module, {module, {module, any}}} |
    {:via, module, {module, {module, any}, any}}
  @type module_key :: {module, any}

  @callback lookup(module_key)                  :: [{pid, any}]
  @callback via_tuple(module_key)               :: via_tuple_type
  @callback via_tuple(module_key, any)          :: via_tuple_type
end
defmodule MoxIssue.ExRegistry do
  @behaviour MoxIssue.ProcessRegistry

  alias MoxIssue.ProcessRegistry

  ########
  # API  #
  ########

  @impl ProcessRegistry
  def lookup({_module, _id} = key), do: Registry.lookup(__MODULE__, key)

  @impl ProcessRegistry
  def via_tuple({_module, _id} = key), do:
    {:via, Registry, {__MODULE__, key}}

  @impl ProcessRegistry
  def via_tuple({_module, _id} = key, value), do:
    {:via, Registry, {__MODULE__, key, value}}

  @spec start_link() :: {:ok, pid} | {:error, any}
  def start_link, do:
    Registry.start_link(keys: :unique, name: __MODULE__)

  #############
  # Callbacks #
  #############

  @spec child_spec(any) :: Supervisor.child_spec
  def child_spec(_) do
    Supervisor.child_spec(
      Registry,
      id: __MODULE__,
      start: {__MODULE__, :start_link, []}
    )
  end
end

Test

The test is rather useless. I am trying to replicate an issue with another app we have, so this is the smallest working example I could come up with:

defmodule MoxIssueTest do
  use ExUnit.Case

  import Mox

  alias MoxIssue.RegistryMock
  alias MoxIssue.ExRegistry
  alias MoxIssue.Worker

  # Make sure mocks are verified when the test exits
  setup :verify_on_exit!

  describe "create/1" do

    test "returns an application ready to use with default values" do

      stub_with(RegistryMock, ExRegistry)

      {:ok, _pid} = Worker.start_link({1})
    end

  end
end

This test fails with:

no expectation defined for MoxIssue.RegistryMock.via_tuple/1 in process #PID<0.163.0>

Which I don’t understand. The whole reason for using stub_with is exactly because I don’t want to expect anything, nor do i want to assert that function X was called Y times. I just want to replace the Mock with an implementation and have it run.

Why am I getting this error?

1 Like

You will probably need to use Mox's global mode. Look here: https://hexdocs.pm/mox/Mox.html#module-multi-process-collaboration

Is the code available to view?

Try

mix test --no-start

I wonder whether it’s a timing issue - i.e. the pool starting before the test is run to register the stub.

Been there, done that, didn’t work =(

This is actually the full code. I have however also created a GitHub repo for your convenience :smiley:

https://github.com/Fl4m3Ph03n1x/mox_issue

Nothing works if I use --no-start because mox won’t even launch :frowning_face:

What was the solution for this?

1 Like