{"id":13509166,"url":"https://github.com/sonerdy/double","last_synced_at":"2025-10-21T15:32:30.281Z","repository":{"id":44715461,"uuid":"78505435","full_name":"sonerdy/double","owner":"sonerdy","description":"Simple injectable test dependencies for Elixir","archived":false,"fork":false,"pushed_at":"2023-06-23T20:47:02.000Z","size":93,"stargazers_count":47,"open_issues_count":1,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-07-05T11:28:35.057Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sonerdy.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}},"created_at":"2017-01-10T06:40:25.000Z","updated_at":"2024-02-12T01:27:26.000Z","dependencies_parsed_at":"2024-01-31T07:53:44.476Z","dependency_job_id":null,"html_url":"https://github.com/sonerdy/double","commit_stats":{"total_commits":74,"total_committers":6,"mean_commits":"12.333333333333334","dds":"0.17567567567567566","last_synced_commit":"4f262b405b31197351e47ee0008bdb513d94fa2a"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sonerdy%2Fdouble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sonerdy%2Fdouble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sonerdy%2Fdouble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sonerdy%2Fdouble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sonerdy","download_url":"https://codeload.github.com/sonerdy/double/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213450238,"owners_count":15589019,"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":[],"created_at":"2024-08-01T02:01:03.919Z","updated_at":"2025-10-21T15:32:30.181Z","avatar_url":"https://github.com/sonerdy.png","language":"Elixir","funding_links":[],"categories":["Testing"],"sub_categories":[],"readme":"# Double\nDouble builds on-the-fly injectable dependencies for your tests.\nIt does NOT override behavior of existing modules or functions.\nDouble uses Elixir's built-in language features such as pattern matching and message passing to\ngive you everything you would normally need a complex mocking tool for.\n\n## Installation\nThe package can be installed as:\n\n  1. Add `double` to your list of dependencies in `mix.exs`:\n\n  ```elixir\n  def deps do\n    [{:double, \"~\u003e 0.8.2\", only: :test}]\n  end\n  ```\n\n## Usage\nStart Double in your `test/test_helper.exs` file:\n\n```elixir\nExUnit.start\nApplication.ensure_all_started(:double)\n```\n\n- [Intro](#modulebehaviour-doubles)\n- Stubs\n    - [Basics](#basics)\n    - [Advanced Return Values](#different-return-values-for-different-arguments)\n    - [Exceptions](#exceptions)\n    - [Verifying Calls](#verifying-calls)\n- [Spies](#spies)\n\n### Module/Behaviour Doubles\nDouble creates a fake module based off of a behaviour or module.\nYou can use this module like any other module that you call functions on.\nEach stub you define will verify that the function name and arity are defined in the target module or behaviour.\n\n```elixir\ndefmodule Example do\n  def process(io \\\\ IO) do # allow an alternative dependency to be passed\n    io.puts(\"It works without mocking libraries!\")\n  end\nend\n\ndefmodule ExampleTest do\n  use ExUnit.Case\n  import Double\n\n  test \"example outputs to console\" do\n    io_stub = stub(IO,:puts, fn(_msg) -\u003e :ok end)\n\n    Example.process(io_stub) # inject the stub module\n\n    # use built-in ExUnit assert_receive/refute_receive to verify things\n    assert_receive({IO, :puts, [\"It works without mocking libraries!\"]})\n  end\nend\n```\n\n## Features\n### Basics\n```elixir\n# Stub a function\ndbl = stub(ExampleModule, :add, fn(x, y) -\u003e x + y end)\ndbl.add(2, 2) # 4\n\n# Pattern match arguments\ndbl = stub(Application, :ensure_all_started, fn(:logger) -\u003e nil end)\ndbl.ensure_all_started(:logger) # nil\ndbl.ensure_all_started(:something) # raises FunctionClauseError\n\n# Stub as many functions as you want\ndbl = ExampleModule\n|\u003e stub(:add, fn(x, y) -\u003e x + y end)\n|\u003e stub(:subtract, fn(x, y) -\u003e x - y end)\n```\n\n### Different return values for different arguments\n```elixir\ndbl = ExampleModule\n|\u003e stub(:example, fn(\"one\") -\u003e 1 end)\n|\u003e stub(:example, fn(\"two\") -\u003e 2 end)\n|\u003e stub(:example, fn(\"three\") -\u003e 3 end)\n\ndbl.example(\"one\") # 1\ndbl.example(\"two\") # 2\ndbl.example(\"three\") # 3\n```\n\n### Multiple calls returning different values\n```elixir\ndbl = ExampleModule\n|\u003e stub(:example, fn(\"count\") -\u003e 1 end)\n|\u003e stub(:example, fn(\"count\") -\u003e 2 end)\n\ndbl.example(\"count\") # 1\ndbl.example(\"count\") # 2\ndbl.example(\"count\") # 2\n```\n\n### Exceptions\n```elixir\ndbl = ExampleModule\n|\u003e stub(:example_with_error_type, fn -\u003e raise RuntimeError, \"kaboom!\" end)\n|\u003e stub(:example_with_error_type, fn -\u003e raise \"kaboom!\" end)\n```\n\n### Verifying calls\nIf you want to verify that a particular stubbed function was actually executed,\nDouble ensures that a message is receivable to your test process so you can just use the built-in ExUnit `assert_receive/assert_received`.\nThe message is a 3-tuple `{module, :function, [arg1, arg2]}`and .\n\n```elixir\ndbl = ExampleModule\n|\u003e stub(:example, fn(\"count\") -\u003e 1 end)\ndbl.example(\"count\")\nassert_receive({ExampleModule, :example, [\"count\"]})\n```\nRemember that pattern matching is your friend so you can do all kinds of neat tricks on these messages.\n```elixir\nassert_receive({ExampleModule, :example, [\"c\" \u003c\u003e _rest]}) # verify starts with \"c\"\nassert_receive({ExampleModule, :example, [%{test: 1}]}) # pattern match map arguments\nassert_receive({ExampleModule, :example, [x]}) # assign an argument to x to verify another way\nassert x == \"count\"\n```\n\n### Module Verification\nBy default your setups will check the source module to ensure the function exists with the correct arity.\n\n```elixir\nstub(IO, :non_existent_function, fn(x) -\u003e x end) # raises VerifyingDoubleError\n```\n\n### Clearing Stubs\nOccasionally it's useful to clear the stubs for an existing double. This is useful when you have\na shared setup and a test needs to change the way a double is stubbed without recreating the whole thing.\n\n```elixir\ndbl = IO\n|\u003e stub(:puts, fn(_) -\u003e :ok end)\n|\u003e stub(:inspect, fn(_) -\u003e :ok end)\n\n# later\ndbl |\u003e clear(:puts) # clear an individual function\ndbl |\u003e clear([:puts, :inspect]) # clear a list of functions\ndbl |\u003e clear() # clear all functions\n```\n\n### Spies\nEverything that works on stubs should pretty much work on spies, but the spy just automatically defaults to using the implementation of the module you're spying.\n\nSay you have already written a stub of some kind for the `IO` module:\n```\ndefmodule IOStub do\n  def write(filename, content) do\n    \"really bad example of writing a file\"\n  end\nend\n```\nNow in your tests you can utilize this stub and \"attach\" spying behavior like so:\n```\ntest \"spying on modules works\" do\n  # use spy/1 instead of stub\n  spy = spy(IOStub)\n\n  # The spy works just like the stub you defined.\n  assert spy.write(\"anything\", \"anything\") == \"really bad example of writing a file\"\n\n  # But it also gives you this!\n  assert_receive {IOStub, :write, [\"anything\", \"anything\"]}\n\n  # The spy can also be stubbed if you want to override a specific function while leaving others\n  stub(spy, :write, fn(_, _) -\u003e :stubbed end)\n  assert spy.write(\"anything\", \"anything\") == :ok\nend\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsonerdy%2Fdouble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsonerdy%2Fdouble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsonerdy%2Fdouble/lists"}