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

wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination 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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
fuelen
Hi all! I want to present a small library which provides a mix task for generating an Entity-Relationship Diagram for Ecto schemas. You...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
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
mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
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
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews