archan937

archan937

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:

Showing Posts 1 to 10

archan937

archan937 OP

Released MecksUnit v0.1.2 in which you can assert function calls with either called (returns a boolean) or assert_called (raises an error when not having found a match):

assert called List.wrap(:foo)
assert_called String.trim(_)
archan937

archan937 OP

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

Hi, did you think, by chance, to add a mocked_test macro that would be compatible with Phoenix test cases? So that you can write then

mocked_test "test name", %{conn: conn} do
 test_body()
 and_assertions()
end

Because If I remember correctly, mocked_test does not accept the second argument with a map returned by setup block.

archan937

archan937 OP

Sure, one moment :sweat_smile:

archan937

archan937 OP

Just released MecksUnit v0.1.4 :slight_smile:

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:

sztosz

sztosz

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_implemented But 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

archan937 OP

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 OP

Well, after some head banging on my desk table whilst digging into the dark caves of ExCoveralls, ExUnit, :meck and :cover I have managed to fix the ExCoveralls related errors :sunglasses:

In other words, MecksUnit v0.1.5 should solve your problems! Please let me know whether that is actually the case or not :muscle:

sztosz

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() in test_helper.exs and

  use MerchantWeb.ConnCase, async: true
  use MecksUnit.Case

  defmock LedgerService do
    def init(_authorization, _uuid), do: :ok
  end

In my test file i get error from the original LedgerService module and then

20:59:51.475 [error] GenServer #PID<0.667.0> terminating
** (stop) {:not_mocked, LedgerService}
    (meck) /home/sztosz/Documents/app-umbrella/deps/meck/src/meck_proc.erl:467: :meck_proc.gen_server/3
    (meck) /home/sztosz/Documents/app-umbrella/deps/meck/src/meck.erl:475: :meck.unload/1
    (elixir) lib/enum.ex:765: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:765: Enum.each/2
    (mecks_unit) lib/mecks_unit/unloader.ex:21: MecksUnit.Unloader.handle_cast/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", {:suite_finished, 8043604, nil}}
20:59:51.479 [error] Task #PID<0.630.0> started from #PID<0.92.0> terminating
** (stop) exited in: GenServer.stop(#PID<0.667.0>, :normal, 30000)
    ** (EXIT) exited in: :sys.terminate(#PID<0.667.0>, :normal, :infinity)
        ** (EXIT) an exception was raised:
            ** (ErlangError) Erlang error: {:not_mocked, LedgerService}
                (meck) /home/sztosz/Documents/app-umbrella/deps/meck/src/meck_proc.erl:467: :meck_proc.gen_server/3
                (meck) /home/sztosz/Documents/app-umbrella/deps/meck/src/meck.erl:475: :meck.unload/1
                (elixir) lib/enum.ex:765: Enum."-each/2-lists^foreach/1-0-"/2
                (elixir) lib/enum.ex:765: Enum.each/2
                (mecks_unit) lib/mecks_unit/unloader.ex:21: MecksUnit.Unloader.handle_cast/2
                (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
                (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
                (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
    (elixir) lib/gen_server.ex:883: GenServer.stop/3
    (ex_unit) lib/ex_unit/event_manager.ex:21: anonymous fn/2 in ExUnit.EventManager.stop/1
    (elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ex_unit) lib/ex_unit/event_manager.ex:20: ExUnit.EventManager.stop/1
    (ex_unit) lib/ex_unit/runner.ex:23: ExUnit.Runner.run/2
    (elixir) lib/task/supervised.ex:89: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:38: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Function: &ExUnit.run/0
    Args: []

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.

Where Next? Top

Trending in Announcing Top

type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
kip
I’ll shortly be launching Text, a nascent text analysis library. Current functionality In this early version (not ready for prime time) ...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews