Fl4m3Ph03n1x

Fl4m3Ph03n1x

Mox mocks not working for other processes

Background

I have a small app where I have a pool that creates several workers. The pool acts as a supervisor for said workers.

Both (the pool and the workers) have a dependency on another module, the ExRegistry. This module registers any process that provides a key.

Test

While using Mox I want to make sure the worker is doing its job correctly, I don’t really want to test the registry just yet. So I stub the RegistryMock with the real one:

stub(RegistryMock, :via_tuple, &ExRegistry.via_tuple/1)
{:ok, _pid} = Worker.start_link({1})

Problem

The problem here is that the pool fails to start:

 Could not start application mox_issue: MoxIssue.Application.start(:normal, []) returned an error: shutdown: failed to start child: MoxIssue.Pool
    ** (EXIT) an exception was raised:
        ** (Mox.UnexpectedCallError) no expectation defined for MoxIssue.RegistryMock.via_tuple/1 in process #PID<0.163.0>

I think this happens because the pool is another process, which also needs access to the RegistryMock but Mox fails to give it access. To test this I set the options on Mox for global so all processes can use the mocks, but it still fails with the same error.

Question

How does Mox behave with multiple processes requiring the same Mock?

Most Liked

ColmB

ColmB

Sorry to drag this thread back up, but it is now the #1 hit for the UnexpectedCallError in the OP. Just to note that beyond the more esoteric situations discussed in the thread so far, you can also see this error via a simple race condition. This situation is discussed in the doco under Blocking on Expectations at Mox — Mox v1.0.1. It also comes up a few times in the closed Mox issues. Basically if the test finishes before any spawned processes then Mox will tear down the expectations before the tested behaviour uses it. Can be very confusing as you may partial mock hits where it is set to expect multiple hits. One way around it is to force the test to wait by messaging from within the mock. From that doco:

test "calling a mock from a different process" do
  parent = self()
  ref = make_ref()

  expect(MyApp.MockWeatherAPI, :temp, fn _loc ->
    send(parent, {ref, :temp})
    {:ok, 30}
  end)

  spawn(fn -> MyApp.HumanizedWeather.temp({50.06, 19.94}) end)

  assert_receive {^ref, :temp}

  verify!()
end

Hope that helps someone in a similar situation!

peerreynders

peerreynders

This approach seems to work (when setup :set_mox_global doesn’t):

defmodule MockShare do
  import Mox

  def init(args) do
    MyApp.CalcMock
    |> expect(:add, fn x, y -> x + y end)
    |> expect(:mult, fn x, y -> x * y end)

    {:ok, args}
  end

  def handle_call({:share, pid}, _from, state) do
    Mox.allow(MyApp.CalcMock, self(), pid)
    {:reply, :ok, state}
  end

  # ---

  def child_spec(_args),
    do: %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, []},
      restart: :permanent,
      shutdown: 5000,
      type: :worker
    }

  def start_link(),
    do: GenServer.start_link(__MODULE__, [])

  def share(share_id, pid),
    do: GenServer.call(share_id, {:share, pid})
end

defmodule MyAppTest do
  use ExUnit.Case, async: true

  import Mox

  def share_mock(_context) do
    {:ok, share_pid} = start_supervised(MockShare)

    [share_pid: share_pid]
  end

  setup_all :share_mock
  setup :verify_on_exit!

  test "Use add expectation in task", context do
    MockShare.share(context.share_pid, self())

    task = Task.async(fn -> MyApp.CalcMock.add(2, 3) end)
    result = Task.await(task)
    assert result == 5
  end

  test "Use mult expectation in task", context do
    MockShare.share(context.share_pid, self())

    task = Task.async(fn -> MyApp.CalcMock.mult(2, 3) end)
    result = Task.await(task)
    assert result == 6
  end
end

Mox.allow/3


The only way I could get global mode to work:

defmodule MyAppTest do
  use ExUnit.Case

  import Mox

  # setup :set_mox_global - doesn't work

  setup_all do
    expect(MyApp.CalcMock, :add, fn x, y -> x + y end)
    expect(MyApp.CalcMock, :mult, fn x, y -> x * y end)
    set_mox_global()
    %{}
  end

  test "Use add expectation in task" do
    task = Task.async(fn -> MyApp.CalcMock.add(2, 3) end)
    result = Task.await(task)
    assert result == 5
  end

  test "Use mult expectation in task" do
    task = Task.async(fn -> MyApp.CalcMock.mult(2, 3) end)
    result = Task.await(task)
    assert result == 6
  end
end

The way I understand the documentation this should only work in global mode …

defmodule MyAppTest do
  use ExUnit.Case, async: true

  import Mox

  setup [:set_mox_private, :verify_on_exit!]

  def add,
    do: MyApp.CalcMock.add(2, 3)

  def mult,
    do: MyApp.CalcMock.mult(2, 3)

  test "Use add expectation in task" do
    expect(MyApp.CalcMock, :add, fn x, y -> x + y end)
    task = Task.async(&add/0)
    result = Task.await(task)
    assert result == 5
  end

  test "Use mult expectation in task" do
    expect(MyApp.CalcMock, :mult, fn x, y -> x * y end)
    task = Task.async(&mult/0)
    result = Task.await(task)
    assert result == 6
  end
end

and yet the task process has no problem using the mocked functions.

al2o3cr

al2o3cr

There’s a note about this below the example - on Elixir 1.8+ Mox can use $callers to detect when a parent process has an expectation set. (see also the similar machinery in DBConnection)

That’s not going to help for your case, though, since the test process and the registry aren’t related in a parent->child sense.

hubertlepicki

hubertlepicki

I don’t know if you should be using it like that in first place. You are attempting to mock the piece of infrastructure you are in control, and mocking/stubbing in my mind should be reserved to dealing with elements of infrastructure you don’t have control over: like remote services/endpoints, systems talking to external systems etc.

If you don’t want to touch ExRegistry in your tests, you could write a testing module that does something similar that makes sense in your test and inject it in it’s place for testing purposes, or isolate the worker further and test it in isolation.

peerreynders

peerreynders

A quick scan of the source code suggests to me that expectations are registered by process id - if that is true the same expectation would have to registered by/for the pool process.

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
Phillipp
Hey, I have a NanoPi-M3 and try to install Elixir on their Ubuntu image. I followed the Raspberry Pi installation instructions from the ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement