TIL: How to test GenServers with Mox
Come for the explainer, stay for the poetry
We recently replaced a hand-rolled test controller with Mox for testing in our umbrella app (how and why are detailed in “Elixir Test Mocking with Mox”). Other than the numerous times we swore Mox wasn’t connected (but it was actually just our broken tests), there was one place in particular that stumped us: testing processes that involved GenServers.
In our app, we’re using GenServers to manage the transactional process of creating organizations and repositories on GitHub. They initiate separate processes for a number of these requests and clean up individual pieces if one of them fails.
When we run the tests, however, we get the following error message:
20:36:40.959 [error] GenServer #PID<0.930.0> terminating
** (Mox.UnexpectedCallError) no expectation defined for GithubClientMock.find_or_create_org/1 in process #PID<0.930.0>
The important bit in the error message is that it specifies the GenServer’s PID as the failing process. So we can see that the process itself doesn’t get access to the method we’re supplying with the mock.
The friction that we’re experiencing is fundamental in Mox. From the documentation:
“All expectations are defined based on the current process.”
Therefore, unless we grant it explicitly, our GenServer process won’t have access to Mox’s state of this process, which is comprised of the available functions.
Mox does provide for multiple processes to access mocks; it just doesn’t do so by default. There are two ways we can make these available: explicit allowances or by using the
set_mox_global macro in a setup block.
- Explicit allowances work by passing the mock to Mox’s
allowfunction, giving access only to the child processes of the current one.
set_mox_globalallows any child process to access the mocks and stubs we define for the current process.
Which one to use?
If you’ve configured your tests to run asynchronously, you’ll want to use
allow and grant explicit access to the child processes for your stubs. Keep in mind that you can only invoke
:async on your tests when they aren’t changing global state.
If your tests are not running in
:async mode, it’s safe to use the
set_mox_global macro, granting stub access to all your children.
Thanks for reading! Want to work on a mission-driven team that’s equally gifted at testing and poetry? We’re hiring!