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
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.2.0. 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
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
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
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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








