Here is a short article and video using meck in Elixir.
If you’re pretty comfortable with Elixir but are unfamiliar with meck, then the article should be enough.
Otherwise, the video might be useful. It’s one take, using my laptop microphone. My apologies for its roughness.
Easy Mocking in Elixir with Meck from Jim Whiteman on Vimeo.
You can grab the code from the video here.
Here is our hypothetical setup: a module named Math that supports addition, subtraction and multiplication.
Let’s pretend that multiplication is special and we have to introduce a dependency to get the job done.
# math.ex
defmodule Math do
def add(x, y) do
x + y
end
def sub(x, y) do
x - y
end
def mul(x, y) do
SomeDependency.mul(x, y)
end
end
# math_test.exs
defmodule MathTest do
use ExUnit.Case
test "addition" do
assert Math.add(1, 1) == 2
end
test "subtraction" do
assert Math.sub(1, 1) == 0
end
test "multiplication" do
assert Math.mul(2, 3) == 6
end
end
Let’s also pretend that SomeDependency.mul
is pretty expensive to run.
We’ll imagine it makes a bunch of HTTP requests and then defrags a couple of hard disks for good measure.
It’s useful enough that we need it, but it’s a giant boat anchor weighing down
math_test.exs. Every time we hit multiplication in our test, SomeDependency.mul
comes along for the ride.
As a result, our math_test runs very slowly.
So, let’s bring :meck
into the mix to mock it out and bring sanity back to our tests.
defmodule Math.Mixfile do
# ...
defp deps do
[{:meck, "~> 0.8.2", only: :test}]
end
end
To make things work we’ll make 3 invocations, :meck.new
, :meck.expect
and :meck.unload
.
# math_test.exs
defmodule MathTest do
test "offers multiplication" do
home = self
:meck.new(SomeDependency) # setup
:meck.expect(
SomeDependency,
:mul,
fn(2, 3) -> send home, :called! end
)
Math.mul(2, 3)
assert_receive :called!, 10
:meck.unload(SomeDependency) # teardown
end
end
:meck.new
creates a new mocked module based on what you pass in. It has a
decent
number of options on its own.
:meck.expect
is where the magic happens. You pass in the module and function
you wish to mock, and pass in the function you want to replace it with.
In the above test, I’m not really testing that multiplication works anymore, only that it hits some particular service named SomeDependency.
So instead of returning a calculation, I’m just sending a message back home with :called!
, and then asserting it ends up in our mailbox within a 100th of a second or so.
You could set this up a million different ways. There is nothing special about the symbol :called!, nor do you need to send a message back to yourself here. The replacement function can be whatever you want it to be. This is just what I chose for this example.
:meck.unload
unloads the module from memory, cleaning things up for the rest of
your tests.
And that’s that.
In the above test case, the real implementation of SomeDependency.mul
won’t be touched, and fn(2, 3) -> send home, :called! end
will be called instead.
Impressive!
:meck.new
and :meck.unload
feel like boilerplate to me and should probably
live in setup
and on_exit
, respectively.
Instead, though, let’s create an easy macro to take care of the setup and teardown for us on a per-test basis.
# test_helper.exs
ExUnit.start()
defmodule TestHelpers do
defmacro mocking(module, fun, replacement, do: body) do
quote do
:meck.new(unquote(module))
:meck.expect(
unquote(module),
unquote(fun),
unquote(replacement)
)
unquote(body)
:meck.unload(unquote(module))
end
end
end
This turns our test case into this:
test "offers multiplication" do
home = self
mocking(SomeDependency,:mul,fn(2, 3) -> send home, :called! end) do
Math.mul(2, 3)
assert_receive :called!, 10
end
end
Not too shabby.
Actually, jjh42’s nice Elixir wrapper for mech does something similar. But it wasn’t too difficult to roll something simple for ourselves.
At any rate, there is a ton more you can do with Meck. If you’d like to use it, it would be a good use of your time to take a look at the project’s README.
The only thing I’d like to add is that I’m not entirely convinced that this is the Elixir way to do things. In particular, I don’t see it used too heavily out in the wild.
The advice I tend to hear instead is, “put as much of your logic as you can in stateless modules”. Maybe if 90% of your code is setup that way, then mocking in this manner becomes less of an issue. Or not. I’m still figuring this out myself.
Regardless, it’s a pretty slick library that feels comfortable, especially coming from Ruby.