archan937

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:

  1. You cannot use async: true
  2. 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:

  1. Add use MecksUnit.Case at the beginning of your test file
  2. Use defmock as if you would define the original module with defmodule containing mocked functions
  3. Use mocked_test as if you would define a normal ExUnit test after 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 :wink:

Most Liked

archan937

archan937

Just released MecksUnit v0.1.4 :slight_smile:

archan937

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

sztosz

I don’t know what happened but since 1.3 mocked_test fail with same defmock do block. Instead of mocking it’s passing through function.
Nevermind, I forgot to do MecksUnit.mock() Working like a charm :+1:

archan937

archan937

Lol. I have been debugging excoveralls related 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

archan937

It is not the umbrella app which causes the problem (successfully using MecksUnit with an umbrella app myself).

A new version has been released. Tackling the error when trying to unload a non-mocked module. Your problem should be solved with MecksUnit v0.1.6. Please confirm, ok?

Where Next?

Popular in Libraries Top

tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
New
mathieuprog
Hello :wave: Allow me to introduce you to Tz, an alternative time zone database support to Tzdata. Why another library? First and fore...
New
sabiwara
Dune is a sandbox for Elixir and aims to safely evaluate user-provided code. You can try it out using this basic Elixir playground made ...
New
seancribbs
Today I released a new dialyzer Mix task as the dialyzex package! At the time we started writing this task, the existing dialyzer integra...
New
praveenperera
FastRSS Parse RSS feeds very quickly: This is rust NIF built using rustler Uses the RSS rust crate to do the actual RSS parsing Speed...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
ahamez
Hi everyone, I’ve been working on this protobuf library for 3 years. We use it in the company I work for, EasyMile, to communicate with ...
New
wmnnd
Hi there, for my project DBLSQD, I needed a file storage solution that is a bit more flexible than Arc. Because I thought others might f...
New
bryanjos
Hi, I just published version 0.23.0 of Elixirscript. Most of the changes are around JavaScript interop now that Elixirscript uses the ...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New

Sub Categories:

We're in Beta

About us Mission Statement