archan937
MecksUnit: Elegantly mock module functions in (async) ExUnit tests
It is a well-know topic within the Elixir community: “To mock or not to mock? :)”
Every alchemist probably has his / her own opinion concerning this topic. José Valim and Plataformatec has published the Hex package Mox which complies with his article on mocking in Elixir.
Personally, I’m not convinced in having to change the code “in service of” testing certain modules. Why would one add abstraction to code of which its purpose isn’t supposed to be interchangeable (with mock modules for instance)?
After some Googling, I found Espec of which I thought that that’s a little bit too much. Finally, I found Mock which could have done the job. But there are two downsides:
- You cannot use
async: true - Defining the mock functions could have been done in a more readable way
Based on that, I decided to write MecksUnit which solves just that. An example:
defmodule Foo do
def trim(string) do
String.trim(string)
end
end
defmodule MecksUnitTest do
use ExUnit.Case, async: true
use MecksUnit.Case
defmock String do
def trim(" Paul "), do: "Engel"
def trim(" Foo ", "!"), do: "Bar"
def trim(_, "!"), do: {:passthrough, [" Surprise! !!!!", "!"]}
def trim(_, _), do: :passthrough
end
defmock List do
def wrap(:foo), do: [1, 2, 3, 4]
end
mocked_test "using mocked module functions" do
task =
Task.async(fn ->
assert "Engel" == String.trim(" Paul ")
assert "Engel" == Foo.trim(" Paul ")
assert "Bar" == String.trim(" Foo ", "!")
assert " Surprise! " == String.trim(" Paul ", "!")
assert "MecksUnit" == String.trim(" MecksUnit ")
assert "Paul Engel" == String.trim(" Paul Engel ", " ")
assert [1, 2, 3, 4] == List.wrap(:foo)
assert [] == List.wrap(nil)
assert [:bar] == List.wrap(:bar)
assert [:foo, :bar] == List.wrap([:foo, :bar])
end)
Task.await(task)
end
test "using the original module functions" do
task =
Task.async(fn ->
assert "Paul" == String.trim(" Paul ")
assert "Paul" == Foo.trim(" Paul ")
assert " Foo " == String.trim(" Foo ", "!")
assert " Paul " == String.trim(" Paul ", "!")
assert "MecksUnit" == String.trim(" MecksUnit ")
assert "Paul Engel" == String.trim(" Paul Engel ", " ")
assert [:foo] == List.wrap(:foo)
assert [] == List.wrap(nil)
assert [:bar] == List.wrap(:bar)
assert [:foo, :bar] == List.wrap([:foo, :bar])
end)
Task.await(task)
end
defmock String do
def trim(" Paul "), do: "PAUL :)"
end
defmock List do
def wrap([1, 2, 3, 4]), do: [5, 6, 7, 8]
def wrap(nil), do: ~w(Surprise)
end
mocked_test "using different mocked module functions" do
task =
Task.async(fn ->
assert "PAUL :)" == String.trim(" Paul ")
assert "PAUL :)" == Foo.trim(" Paul ")
assert " Foo " == String.trim(" Foo ", "!")
assert " Paul " == String.trim(" Paul ", "!")
assert "MecksUnit" == String.trim(" MecksUnit ")
assert "Paul Engel" == String.trim(" Paul Engel ", " ")
assert [:foo] == List.wrap(:foo)
assert ["Surprise"] == List.wrap(nil)
assert [:bar] == List.wrap(:bar)
assert [:foo, :bar] == List.wrap([:foo, :bar])
assert [5, 6, 7, 8] == List.wrap([1, 2, 3, 4])
end)
Task.await(task)
end
end
Mocking module functions is pretty straightforward and done as follows:
- Add
use MecksUnit.Caseat the beginning of your test file - Use
defmockas if you would define the original module withdefmodulecontaining mocked functions - Use
mocked_testas if you would define a normal ExUnittestafter having defined all the required mock modules
The defined mock modules only apply to the first mocked_test encountered. So they are isolated (despite of :meck having an unfortunate global effect ) as MecksUnit takes care of it. Also, non-matching function heads within the mock module will result in invoking the original module function as well. And last but not least: you can just run the tests asynchronously .
Enjoy using MecksUnit (if you prefer unobtrusive mocking). A Github star is very welcome, haha ![]()
Trending in Announcing
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #performance
- #security










First 10 of 23 Posts
archan937
Released MecksUnit v0.1.2 in which you can assert function calls with either
called(returns a boolean) orassert_called(raises an error when not having found a match):archan937
MecksUnit v0.1.3 is out. It includes the fix for :meck related compile errors which often occurred when mocking within multiple files
sztosz
Hi, did you think, by chance, to add a
mocked_testmacro that would be compatible with Phoenix test cases? So that you can write thenBecause If I remember correctly,
mocked_testdoes not accept the second argument with a map returned bysetupblock.archan937
Sure, one moment
archan937
Just released MecksUnit v0.1.4
sztosz
I don’t know what happened but since 1.3 mocked_test fail with samedefmock doblock. Instead of mocking it’s passing through function.Nevermind, I forgot to do
MecksUnit.mock()Working like a charmsztosz
There is some conflict with GitHub - parroty/excoveralls: Coverage report tool for Elixir with coveralls.io integration. · GitHub, because when you run tests with coveralls and add some options, the mock is not registered, and test uses original module instead of mocked one
example command that fails
mix coveralls.html -u --exclude not_implementedBut I was unable to pinpoint the cause.It works OK with MecksUnit 1.2 but not with 1.3 there must be some change there that makes it fail.
archan937
Lol. I have been debugging
excoverallsrelated issues myself at the moment. I’m almost there, just need to figure out on how to hook in after the test suite has finished and just before excoveralls does his thing. Stay tuned, I hope to have it solved within a few hours.archan937
Well, after some head banging on my desk table whilst digging into the dark caves of
ExCoveralls,ExUnit,:meckand:coverI have managed to fix the ExCoveralls related errorsIn other words, MecksUnit v0.1.5 should solve your problems! Please let me know whether that is actually the case or not
sztosz
Somehow I’m still unable to run it with phoenix controller tests. I’ve spent on it only few minutes though, because of time constrains this week. with
MecksUnit.mock()intest_helper.exsandIn my test file i get error from the original LedgerService module and then
But when I run single test, or just test in one file the tests pass.
When that one whole app from that umbrella or all apps in umbrella it fails.