SkillAgentSearch skills...

Mock

Mocking library for Elixir language

Install / Use

/learn @jjh42/Mock
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Build Status

Mock

A mocking library for the Elixir language.

We use the Erlang meck library to provide module mocking functionality for Elixir. It uses macros in Elixir to expose the functionality in a convenient manner for integrating in Elixir tests.

See the full reference documentation.

Table of Contents

Installation

First, add mock to your mix.exs dependencies:

def deps do
  [{:mock, "~> 0.3.0", only: :test}]
end

and run $ mix deps.get.

with_mock - Mocking a single module

The Mock library provides the with_mock macro for running tests with mocks.

For a simple example, if you wanted to test some code which calls HTTPotion.get to get a webpage but without actually fetching the webpage you could do something like this:

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "test_name" do
    with_mock HTTPotion, [get: fn(_url) -> "<html></html>" end] do
      assert "<html></html>" == HTTPotion.get("http://example.com")
    end
  end
end

The with_mock macro creates a mock module. The keyword list provides a set of mock implementation for functions we want to provide in the mock (in this case just get). Inside with_mock we exercise the test code and we can check that the call was made as we expected using called and providing the example of the call we expected.

with_mocks - Mocking multiple modules

You can mock up multiple modules with with_mocks.

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "multiple mocks" do
    with_mocks([
      {Map,
       [],
       [get: fn(%{}, "http://example.com") -> "<html></html>" end]},
      {String,
       [],
       [reverse: fn(x) -> 2*x end,
        length: fn(_x) -> :ok end]}
    ]) do
      assert Map.get(%{}, "http://example.com") == "<html></html>"
      assert String.reverse(3) == 6
      assert String.length(3) == :ok
    end
  end
end

The second parameter of each tuple is opts - a list of optional arguments passed to meck.

test_with_mock - with_mock helper

An additional convenience macro test_with_mock is supplied which internally delegates to with_mock. Allowing the above test to be written as follows:

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test_with_mock "test_name", HTTPotion,
    [get: fn(_url) -> "<html></html>" end] do
    HTTPotion.get("http://example.com")
    assert_called HTTPotion.get("http://example.com")
  end
end

The test_with_mock macro can also be passed a context argument allowing the sharing of information between callbacks and the test

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  setup do
    doc = "<html></html>"
    {:ok, doc: doc}
  end

  test_with_mock "test_with_mock with context", %{doc: doc}, HTTPotion, [],
    [get: fn(_url, _headers) -> doc end] do

    HTTPotion.get("http://example.com", [foo: :bar])
    assert_called HTTPotion.get("http://example.com", :_)
  end
end

setup_with_mocks - Configure all tests to have the same mocks

The setup_with_mocks mocks up multiple modules prior to every single test along while calling the provided setup block. It is simply an integration of the with_mocks macro available in this module along with the setup macro defined in elixir's ExUnit.

defmodule MyTest do
  use ExUnit.Case, async: false
  import Mock

  setup_with_mocks([
    {Map, [], [get: fn(%{}, "http://example.com") -> "<html></html>" end]}
  ]) do
    foo = "bar"
    {:ok, foo: foo}
  end

  test "setup_with_mocks" do
    assert Map.get(%{}, "http://example.com") == "<html></html>"
  end
end

The behaviour of a mocked module within the setup call can be overridden using any of the methods above in the scope of a specific test. Providing this functionality by setup_all is more difficult, and as such, setup_all_with_mocks is not currently supported.

Currently, mocking modules cannot be done asynchronously, so make sure that you are not using async: true in any module where you are testing.

Also, because of the way mock overrides the module, it must be defined in a separate file from the test file.

Mocking input dependent output

If you have a function that should return different values depending on what the input is, you can do as follows:

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "mock functions with multiple returns" do
    with_mock(Map, [
      get: fn
        (%{}, "http://example.com") -> "<html>Hello from example.com</html>"
        (%{}, "http://example.org") -> "<html>example.org says hi</html>"
        (%{}, url) -> conditionally_mocked(url)
      end
    ]) do
      assert Map.get(%{}, "http://example.com") == "<html>Hello from example.com</html>"
      assert Map.get(%{}, "http://example.org") == "<html>example.org says hi</html>"
      assert Map.get(%{}, "http://example.xyz") == "<html>Hello from example.xyz</html>"
      assert Map.get(%{}, "http://example.tech") == "<html>example.tech says hi</html>"
    end
  end

  def conditionally_mocked(url) do
    cond do
      String.contains?(url, ".xyz") -> "<html>Hello from example.xyz</html>"
      String.contains?(url, ".tech") -> "<html>example.tech says hi</html>"
    end
  end
end

Mocking functions with different arities

You can mock functions in the same module with different arity:

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "mock functions with different arity" do
    with_mock String,
      [slice: fn(string, range)      -> string end,
       slice: fn(string, range, len) -> string end]
    do
      assert String.slice("test", 1..3) == "test"
      assert String.slice("test", 1, 3) == "test"
    end
  end
end

Mock repeated calls

You can mock repeated calls to the same function and arguments to return different results in a series using the in_series call with static values. This does not currently support functions.

Caution: This is only useful in rare instances where the underlying business logic is likely to be stateful. If you can avoid it by using different function arguments, or refactor the function to be stateful, consider that approach first.

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "mock repeated calls with in_series" do
    with_mock String,
      [slice: [in_series(["test", 1..3], ["string1", "string2", "string3"])]]
    do
      assert String.slice("test", 1..3) == "string1"
      assert String.slice("test", 1..3) == "string2"
      assert String.slice("test", 1..3) == "string3"
    end
  end
end

passthrough - partial mocking of a module

By default, only the functions being mocked can be accessed from within the test. Trying to call a non-mocked function from a mocked Module will result in an error. This can be circumvented by passing the :passthrough option like so:

defmodule MyTest do
  use ExUnit.Case, async: false
  import Mock

  test_with_mock "test_name", IO, [:passthrough], [] do
    IO.puts "hello"
    assert_called IO.puts "hello"
  end
end

Assert called - assert a specific function was called

You can check whether or not your mocked module was called.

Assert called - specific value

It is possible to assert that the mocked module was called with a specific input.

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "test_name" do
    with_mock HTTPotion, [get: fn(_url) -> "<html></html>" end] do
      HTTPotion.get("http://example.com")
      assert_called HTTPotion.get("http://example.com")
    end
  end
end

Assert called - wildcard

It is also possible to assert that the mocked module was called with any value by passing the :_ wildcard.

defmodule MyTest do
  use ExUnit.Case, async: false

  import Mock

  test "test_name" do
    with_mock HTT

Related Skills

View on GitHub
GitHub Stars690
CategoryDevelopment
Updated1mo ago
Forks80

Languages

Elixir

Security Score

95/100

Audited on Feb 1, 2026

No findings