I was recently looking at mocking frameworks for Erlang, to facilitate unit testing. Mocking is a mechanism for easily producing objects and functionality to limit the scope of your tests. For example:
-module(a). -export([a/1]). a(X) -> b(X) * X. b(X) -> X * 5.
b/1 does something like go to a database, or connect to
some other remote service, or read from a file… something that may
either take a long time, or might not be available in a test
environment. For a unit test for
a/1, what you’re really concerned
with is testing the functionality of
b/1; to accomplish this,
you’d like to mock up the functionality of
b/1 to be something known,
so that you can prove
What you should be able to do with mocking is something like this (there are a lot of different ways that you could define the mocking interface; I’m just picking an arbitrary one):
mock:expect( ?MODULE, b, fun(X) -> X end ).
Then you could define a unit test that says:
4 = a(2).
a/1 provable through induction, because it isolates the code
a/1 – which is exactly what you want in a unit test.
Unfortunately, you can’t do this in Erlang in such a way that the unit
tests don’t impact the way you write the code being tested. This is
due to a flaw in Erlang modules, and particularly, the
If you want to do mocking sample above in Erlang, the code being tested must become
-module(a). -export([a/1,b/1]). a(X) -> a:b(X) * X. b(X) -> X * 5.
This is because mocking replaces the functionality of a function with
different functionality, and the Erlang compiler hard-links the call to
a/1 unless you specify the module of
b/1. This, in itself, is
not a problem: the problem is that even within module a, references to
functions within the same module must be exported. That is, if
wants to call
b/1 must be exported. Why is this a problem?
Because it leads to comments like this, which you find all too commonly
in Erlang projects:
-export([a/1]). % ------------------------------------- % WARNING: DO NOT CALL THESE FUNCTIONS -export([b/1]).
There are a number of reasons why the authors had to export
spawn/3 can’t call functions that aren’t exported. Because
mocking requires it. Because they want to support code reloading. All
of which are valid use cases, and all of which force developers to
over-expose their API, breaking encapsulation, and suggesting that
Erlang’s module handling is flawed.