The (not so) Magic Tricks of Testing in Elixir (2/2)
How we use Mox to write better tests in Elixir
In the first part of this post we described various approaches to test Elixir applications, and ended it by pointing out some of the shortcomings of the approaches mentioned, particularly when we need to create mocks. Let’s recap them here:
- The testing logic is spread out across diferrent files, which makes them hard to reason about;
- These solutions still allow us to create ad hoc mocks (i.e. mocks that aren’t based on behaviours).
Lately at Onfido we’ve been using the Mox library in some of our Elixir projects, to solve both issues presented above. Mox was created by José Valim, about four months ago. It follows the principles described in his famous blog post Mocks and explicit contracts (by the way, a highly recommended read), and allows the creation of concurrent mocks in Elixir. Its principles are:
- No ad-hoc mocks. You can only create mocks based on behaviours
- No dynamic generation of modules during tests. Mocks are preferably defined in your
test_helper.exsor in a
setup_allblock and not per test
- Concurrency support. Tests using the same mock can still use
- Rely on pattern matching and function clauses for asserting on the input instead of complex expectation rules
We will now see how we would test the module described in the first part using the Mox library. First of all, we add it as a dependency:
Then we have to define our mock. This is usually done in the
test_helper.exs file, although you can also define them in a
setup_all block. In this case we’ll do it in
Now we need to change the test itself to start using the library:
First we import
Mox so that we can use its functions without using the fully-qualified name. In the test itself, we pipe the mock defined in
test_helper.exs to the
expect function. This way we’re expecting that
emit will be called on our mock module. When this happens, the lambda that is passed as third argument is the function that will be executed. Moreover, we’re using pattern match on the argument of the lambda function, which means that we’re expecting the
emit function to be called with
"validate_address" . If it doesn’t, the match will fail, along with the test. The body of the lambda function just sends a message to the
self()(which returns the PID of the current process), and then we use
assert_receive to check that we have that message on the mailbox of the current process.
Lastly, we use
setup :verify_on_exit! to ensure that the expectations that we defined on our mocks are met. You can also use
verify(mock_module) if you want to control when the verification is made, and don’t want to do it when the test exists.
We’re essentially following the same strategy as we did in the first part of this post (of sending a message to
self()), but now, by using
Mox, we have some noteworthy benefits:
- Our mocks are always defined based on a behaviour. Note that we defined our behaviour for
Demo.Events.EventEmitteron the first part of this post. If we hadn’t,
Moxwill raise an
ArgumentError, stating that the module you’re passing to
defmockis not a behaviour. This will ensure that the mocks we’re using in our tests are always respecting the contract of the real implementation. As applications grow bigger and bigger, it’s common to see tests where mocks are no longer following the API of the actual implementation. This may be very dangerous, as this leads to running your tests on an unreal environment, which in turn leads to having all of your tests green but things blowing up in your production environment.
- By using
Moxour tests become much more readable, since you can know at a glance what’s happening with that test. In our example, we can quickly know that we’re using a mock, and that when
emitis called upon that mock we’re sending a message to the current process. On the example of the first part, the test contained no information about the usage of mocks. You just saw an
assert_receive, and to figure out what was going on you had to go to the module under test, see that there was a dependency being injected, go to
config/test.exsto know what’s being injected in the test environment, and finally go to that module to see that we’re sending a message to the current process. By having this inlined in our test, we can save all of these jumps and immediately know what’s going on in that test.
What if we’re creating new processes on the module under test?
In the first part there’s a section where we talk about what happens when the module that we’re testing is creating a new process before producing the side-effect we’re looking to confirm. We changed the mock module to a
GenServer, so that we can keep track of the list of processes we want to send messages to. Let’s see how we’d achieve the same thing using
The test itself remains the same, but we had to change two things:
setupphase. This way any process can consume the mocks defined in our test.
async: falsein our test. Since now our mock may be shared by multiple processes, we have to set this option so that this test doesn’t run concurrently. Note that you can also use
Moxwill set its own mode to private or global, depending on whether
asyncis set to
To wrap-up this post, I want to emphasize that, using
Mox, you can’t create mocks that aren’t based on behaviours, as its first principle states. This means that you won’t be able to create a mock for a third-party library if it doesn’t define behaviours for its modules. This brings me to this phrase, well-known in TDD communities:
Don’t mock what you don’t own
This phrase is a bit counter intuitive, since most of the mocks you want to create are to control interactions with external parties. But what it actually means is that you shouldn’t just rely on mocks for things that are out of your control.
Let’s explore this further with an example: say that you use the
ExTwitter library to access Twitter. In order to follow this principle, you’d create a wrapper around the ExTwitter library, and this wrapper will define the contract between your application and the ExTwitter library. It’s in this wrapper module that you’ll also define the behaviour. Then, the mock that you’ll create is for the wrapper, and not for
ExTwitter itself. This wrapper will most of the times be a subset of the
This change may cause some friction at first, especially if you’re used to creating ad-hoc mocks, but remember that this will hugely increase the mid- and long-term maintainability of your application. Furthermore, starting to use
Mox will surely spark some healthy discussions inside your team about testing in general, and mocks in particular. Take this opportunity to define how and when you’ll use mocks (also taking the Test Pyramid into account), which will ensure that you have a consistent approach to testing throughout your application(s). In turn, this will make your tests easier to follow and also help on the onboarding of new team members.
I hope you’ve enjoyed this post about testing in Elixir. See you next time!
Note: This post is an adaptation of a talk I gave at Lisbon |> Elixir meetup.