Struggling with the Mock noun

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 :slight_smile:

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.

1 Like

mox is a quite elegant solution. The only ā€œproblemā€ is that my code often uses tasks like this

bar_tasks = for bar <- links, do: Task.async(fn -> Api.get(bar) end)

where Api is the mox:ed module which breaks the mocks since we leave the test process.

Hmm, I wonder if I can mock the the Task moduleā€¦ It feels like a bad ideaā€¦

1 Like

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 :slight_smile:

@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.

1 Like

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.

1 Like

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!

7 Likes

We have just shipped Mox v0.3.0 with global and private modes. Thanks @hubertlepicki for the patch!

Now I hope the library is truly done. Famous last words, I know. :slight_smile:

6 Likes

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.

1 Like