{"id":13513817,"url":"https://github.com/edgurgel/mimic","last_synced_at":"2025-05-14T05:10:37.304Z","repository":{"id":32992473,"uuid":"73606875","full_name":"edgurgel/mimic","owner":"edgurgel","description":"A mocking library for Elixir","archived":false,"fork":false,"pushed_at":"2025-04-02T07:30:22.000Z","size":634,"stargazers_count":486,"open_issues_count":11,"forks_count":39,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-11T03:58:08.237Z","etag":null,"topics":["elixir","expect","mock","mocks","stub","testing","tests"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/mimic","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edgurgel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-11-13T09:41:25.000Z","updated_at":"2025-05-04T03:20:59.000Z","dependencies_parsed_at":"2023-11-07T08:31:12.742Z","dependency_job_id":"e9272922-7c95-417a-a6fa-8b2d5fbe1fe9","html_url":"https://github.com/edgurgel/mimic","commit_stats":{"total_commits":72,"total_committers":19,"mean_commits":3.789473684210526,"dds":"0.41666666666666663","last_synced_commit":"cf5cda5d03e613ac71fe4d6ecbbf1db23c1cd09e"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fmimic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fmimic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fmimic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edgurgel%2Fmimic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edgurgel","download_url":"https://codeload.github.com/edgurgel/mimic/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076849,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","expect","mock","mocks","stub","testing","tests"],"created_at":"2024-08-01T05:00:38.218Z","updated_at":"2025-05-14T05:10:37.255Z","avatar_url":"https://github.com/edgurgel.png","language":"Elixir","funding_links":[],"categories":["Elixir"],"sub_categories":[],"readme":"![Mimic logo](logo.png)\n# Mimic\n\n[![CI](https://github.com/edgurgel/mimic/actions/workflows/main.yml/badge.svg)](https://github.com/edgurgel/mimic/actions/workflows/main.yml)\n[![Module Version](https://img.shields.io/hexpm/v/mimic.svg)](https://hex.pm/packages/mimic)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/mimic/)\n[![Total Download](https://img.shields.io/hexpm/dt/mimic.svg)](https://hex.pm/packages/mimic)\n[![License](https://img.shields.io/hexpm/l/mimic.svg)](https://github.com/edgurgel/mimic/blob/master/LICENSE)\n[![Last Updated](https://img.shields.io/github/last-commit/edgurgel/mimic.svg)](https://github.com/edgurgel/mimic/commits/master)\n\nA sane way of using mocks in Elixir. It borrows a lot from both Meck \u0026 Mox! Thanks [@eproxus](https://twitter.com/eproxus) \u0026 [@josevalim](https://twitter.com/josevalim).\n\n## Installation\n\nJust add `:mimic` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:mimic, \"~\u003e 1.10\", only: :test}\n  ]\nend\n```\n\nIf `:applications` key is defined inside your `mix.exs` or you run `mix test --no-start`, you probably want to add `Application.ensure_all_started(:mimic)` in your `test_helper.exs`\n\n## Using\n\nModules need to be prepared so that they can be used.\n\nYou must first call `copy` in your `test_helper.exs` for\neach module that may have the behaviour changed.\n\n```elixir\nMimic.copy(Calculator)\n\nExUnit.start()\n```\n\nCalling `copy` will not change the behaviour of the module.\n\nThe user must call `stub/1`, `stub/3`, `expect/4` or `reject/1` so that the functions can\nbehave differently.\n\nThen for the actual tests one could use it like this:\n\n```elixir\nuse ExUnit.Case, async: true\nuse Mimic\n\ntest \"invokes mult once and add twice\" do\n  Calculator\n  |\u003e stub(:add, fn x, y -\u003e :stub end)\n  |\u003e expect(:add, fn x, y -\u003e x + y end)\n  |\u003e expect(:mult, 2, fn x, y -\u003e x * y end)\n\n  assert Calculator.add(2, 3) == 5\n  assert Calculator.mult(2, 3) == 6\n\n  assert Calculator.add(2, 3) == :stub\nend\n```\n\n## Stub, Expect and Reject\n\n### Stub\n\n`stub/1` will change every module function to throw an exception if called.\n\n```elixir\nstub(Calculator)\n\n** (Mimic.UnexpectedCallError) Stub! Unexpected call to Calculator.add(3, 7) from #PID\u003c0.187.0\u003e\n     code: assert Calculator.add(3, 7) == 10\n```\n\n`stub/3` changes a specific function to behave differently. If the function is not called no verification error will happen.\n\n### Expect\n\n`expect/4` changes a specific function and it works like a queue of operations. It has precedence over stubs and if not called a verification error will be thrown.\n\nIf the same function is called with `expect/4` the order will be respected:\n\n```elixir\nCalculator\n|\u003e stub(:add, fn _x, _y -\u003e :stub end)\n|\u003e expect(:add, fn _, _ -\u003e :expected_1 end)\n|\u003e expect(:add, fn _, _ -\u003e :expected_2 end)\n\nassert Calculator.add(1, 1) == :expected_1\nassert Calculator.add(1, 1) == :expected_2\nassert Calculator.add(1, 1) == :stub\n```\n\n`expect/4` has an optional parameter which is the amount of calls expected:\n\n```elixir\nCalculator\n|\u003e expect(:add, 2, fn x, y -\u003e {:add, x, y} end)\n\nassert Calculator.add(1, 3) == {:add, 1, 3}\nassert Calculator.add(4, 5) == {:add, 4, 5}\n```\n\nWith `use Mimic`, verification `expect/4` function call of is done automatically on test case end. `verify!/1` can be used in case custom verification timing required:\n\n```elixir\nCalculator\n|\u003e expect(:add, 2, fn x, y -\u003e {:add, x, y} end)\n\n# Will raise error because Calculator.add is not called\n# ** (Mimic.VerificationError) error while verifying mocks for #PID\u003c0.3182.0\u003e:\n#   * expected Calculator.add/2 to be invoked 1 time(s) but it has been called 0 time(s)\nverify!()\n```\n\nUsing `expect/4` on intra-module functions will not work, unless the function is referenced by it's fully qualified name.\n\n```elixir\ndefmodule Calculator do\n  def mult(x, y) do\n    x * y\n  end\n\n  def negation(x) do\n    mult(x, -1)\n  end\nend\n\nCalculator\n|\u003e expect(:mult, fn x, y -\u003e x + y end)\n\nassert Calculator.negation(5) == -5\n\n# Will raise error because because BEAM optimises this case and jumps directly to the appropriate bytecode.\n# ** (Mimic.VerificationError) error while verifying mocks for #PID\u003c0.207.0\u003e:\n#   * expected Calculator.mult/2 to be invoked 1 time(s) but it has been called 0 time(s)\nverify!()\n```\n\nTo ensure that the stubbed Mimic function is called, it can be referenced by `Calculator.mult/2` instead of `mult/2`.\n\n### Reject\n\nOne may want to reject calls to a specific function. `reject/1` can be used to achieved this behaviour.\n\n```elixir\nreject(\u0026Calculator.add/2)\nassert_raise Mimic.UnexpectedCallError, fn -\u003e Calculator.add(4, 2) end\n```\n\n## Private and Global mode\n\nThe default mode is private which means that only the process\nand explicitly allowed process will see the different behaviour.\n\nCalling `allow/2` will permit a different pid to call the stubs and expects from the original process.\n\nIf you are using `Task` there is no need to use global mode as Tasks can see the same expectations and stubs from the calling process.\n\nGlobal mode can be used with `set_mimic_global` like this:\n\n```elixir\nsetup :set_mimic_global\n\ntest \"invokes add and mult\" do\n  Calculator\n  |\u003e expect(:add, fn x, y -\u003e x + y end)\n  |\u003e expect(:mult, fn x, y -\u003e x * y end)\n\n  parent_pid = self()\n\n  spawn_link(fn -\u003e\n    assert Calculator.add(2, 3) == 5\n    assert Calculator.mult(2, 3) == 6\n\n    send parent_pid, :ok\n  end)\n\n  assert_receive :ok\nend\n```\n\nThis means that all processes will get the same behaviour\ndefined with expect \u0026 stub. This option is simpler but tests running\nconcurrently will have undefined behaviour. It is important to run with `async: false`.\nOne could use `:set_mimic_from_context` instead of using `:set_mimic_global` or `:set_mimic_private`. It will be private if `async: true`, global otherwise.\n\n## DSL Mode\nTo use DSL Mode `use Mimic.DSL` rather than `use Mimic` in your test.  DSL Mode enables a more expressive api to the Mimic functionality.\n\n```elixir\n  use Mimic.DSL\n\n  test \"basic example\" do\n    stub Calculator.add(_x, _y), do: :stub\n    expect Calculator.add(x, y), do: x + y\n    expect Calculator.mult(x, y), do: x * y\n\n    assert Calculator.add(2, 3) == 5\n    assert Calculator.mult(2, 3) == 6\n\n    assert Calculator.add(2, 3) == :stub\n  end\n```\n\n## Stubs with fake module\n`stub_with/2` enable substitute function call of a module with another similar module.\n\n```elixir\n  defmodule BadCalculator do\n    def add(x, y), do: x*y\n    def mult(x, y), do: x+y\n  end\n\n  test \"basic example\" do\n    stub_with(Calculator, BadCalculator)\n\n    assert Calculator.add(2, 3) == 6\n    assert Calculator.mult(2, 3) == 5\n  end\n```\n\n## Calling the original\n`call_original/3` allows to call original unmocked version of the function.\n\n```elixir\nsetup :set_mimic_private\n\ntest \"calls original function even if it has been is stubbed\" do\n  stub_with(Calculator, InverseCalculator)\n\n  assert call_original(Calculator, :add, [1, 2]) == 3\nend\n```\n\n## Experimental type checking for copied modules\n\nOne can pass `type_check: true` when a module is copied to also get the function expected/stubbed to\nvalidate the arguments and return value using [Ham](https://github.com/edgurgel/ham) which is essentially\nwhat [Hammox](https://github.com/msz/hammox) improved on Mox.\n\n```elixir\nMimic.copy(:cowboy_req, type_check: true)\n```\n\nIf there is any problem with the arguments or return values of the stubbed functions on your tests you might see\nan error like this one:\n\n```elixir\n     ** (Mimic.TypeCheckError) :cowboy_req.parse_qs/1: 1st argument value %{} does not match 1st parameter's type :cowboy_req.req().\n       Could not find a map entry matching required(:method) =\u003e binary().\n```\n\nThis feature is experimental at the moment which means that it might change a little bit how this\nis configured and used. Feedback is welcome!\n\n## Implementation Details \u0026 Performance\n\nAfter calling `Mimic.copy(MyModule)`, calls to functions belonging to this module will first go through an ETS table to check which pid sees what (stubs, expects or call original).\n\nIt is really fast but it won't be as fast as calling a no-op function. Here's a very simple benchmark:\n\n```elixir\ndefmodule Enumerator do\n def to_list(x, y), do: Enum.to_list(x..y)\nend\n```\n\nBenchmarking `Enumerator.to_list(1, 100)` :\n\n```\nName               ips        average  deviation         median         99th %\nmimic         116.00 K        8.62 μs   ±729.13%           5 μs          29 μs\noriginal       19.55 K       51.15 μs   ±302.46%          34 μs         264 μs\n\nComparison:\nmimic         116.00 K\noriginal       19.55 K - 5.93x slower\n```\n\nBenchmarking `Enumerator.to_list(1, 250)` :\n\n```\nName               ips        average  deviation         median         99th %\noriginal      131.49 K        7.61 μs   ±167.90%           7 μs          16 μs\nmimic         105.47 K        9.48 μs   ±145.21%           9 μs          27 μs\n\nComparison:\noriginal      131.49 K\nmimic         105.47 K - 1.25x slower\n```\n\nThere's a small fixed price to pay when mimic is used but it is unnoticeable for tests purposes.\n\n## Acknowledgements\n\nThanks to [@jimsynz](https://github.com/jimsynz)  and [@alissonsales](http://github.com/alissonsales) for all the help! :tada:\n\nThanks to [@mendokusai](https://github.com/mendokusai) for the nice logo!\n\n## Copyright and License\n\nCopyright (c) 2016 Eduardo Gurgel\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fmimic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedgurgel%2Fmimic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedgurgel%2Fmimic/lists"}