It is possible to do it but you need to be careful with not intoducing race conditions between tests. In your example you are safe but if MyServer was a named process then you can run into conflicts. So given the implications I would rather wait for multiple use cases and decide from there
There are some other things which are limited on purpose, like the ability of only mocking behaviours instead of full modules. All of those things could be lifted but I am waiting for strong evidence before I do.
It might be possible to traverse the $ancestors list to find the PID that has the mock registered?
That would allow tests to run concurrently and use mocks from spawned Tasks, Agents, etc.
Thatās a possibility although $ancestors is not guaranteed to be set:
iex(1)> spawn fn -> IO.inspect Process.get :"$ancestors" end
nil
#PID<0.93.0>
Most OTP behaviours do set it though. It also wonāt work with things like processes spawned in supervisors since the ancestor is the supervisor and not the current test/request.
@mbuhot That idea had occurred to me too, but I havenāt had time to explore. It feels less crazy to hear someone else thought of it too
@josevalim Iād hope the supervisorās $ancestors get us closer to the test process. I would expect to keep walking the $ancestors until finding a registered mock. Is that completely untenable?
My current project has had to settle for turning off concurrency in the test suite because we have to exercise code that launches Tasks/GenServers that contact the mocks.
The trouble is that those Task and GenServer should be spawned under a supervisor. In this case, the ancestor will never be a mocked one since it is a supervisor started when the application starts. The only scenario using ancestors would work is if you are not following the OTP principles.
@hubertlepicki is working on improving Mox to make it closer to the sandbox. When done, you will be able to run most tests concurrently. Except scenarios as you described above.
Ah, yes. Now I better understand that supervisor situation. Thanks!
Iām still not giving up though. Concurrency is a major feature of Elixir, and Iād really like to run concurrent code in concurrent tests well. Iāll keep thinking this over.
The true āshared nothingā approach to processes in Erlang makes the task super difficult. I do not believe it will be possible to set up global system-wide mocks, and still be able to run the tests in parallel, without resorting to spawning multiple BEAM instances.
We could do, however, system-wide mocks that can be useful when you do async: false.
What I am currently working on is not even that, however. Instead, we plan to support mocks in concurrent tests where you explicitly grant access to processes you started with MyMock |> allow(self(), other_pid) - that grants access to expectations and stubs defined by current process to some other process. This is already implemented on a branch, just doing some last tweaks.
Then we would have a mechanism to pass the mocks to Phoenix/Cowboy worker processes via HTTP header & a plug. But the mocks would still not be available in supervision tree started in appication callback module. So you can run concurrent tests with mocks, provided you do not need to mock anything there.
We have released Mox v0.2.0 which supports stubs (which are not verified), verify_on_exit (so you no longer need to call verify! at the end of every test) and an allowances mechanism for concurrent testing.
We are now only missing a shared mock mechanism and then hopefully this is done. Thanks to @brandonjoyce and @hubertlepicki for the contributions!
I was going to work on the plug to support ecto-style ownership, but I am actually good with just running the tests in global mode with async: false. I lack motivation to do the plug as I wonāt benefit it much. But I guess we can keep an eye on peopleās need and add it if / when they say they need it.