{"id":14449540,"url":"https://github.com/bravobike/efx","last_synced_at":"2026-02-22T19:01:52.538Z","repository":{"id":240983979,"uuid":"798773217","full_name":"bravobike/efx","owner":"bravobike","description":"A library to declaratively write testable effects","archived":false,"fork":false,"pushed_at":"2025-09-10T06:48:44.000Z","size":167,"stargazers_count":97,"open_issues_count":0,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-02-14T18:44:41.273Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/bravobike.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-10T12:48:28.000Z","updated_at":"2026-01-27T14:26:05.000Z","dependencies_parsed_at":"2024-05-28T22:06:35.692Z","dependency_job_id":"a038f7c5-0279-4802-9637-3aa5da28f14a","html_url":"https://github.com/bravobike/efx","commit_stats":null,"previous_names":["bravobike/efx"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bravobike/efx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bravobike%2Fefx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bravobike%2Fefx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bravobike%2Fefx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bravobike%2Fefx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bravobike","download_url":"https://codeload.github.com/bravobike/efx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bravobike%2Fefx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29723573,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T15:10:41.462Z","status":"ssl_error","status_checked_at":"2026-02-22T15:10:04.636Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-09-01T08:01:21.303Z","updated_at":"2026-02-22T19:01:52.492Z","avatar_url":"https://github.com/bravobike.png","language":"Elixir","funding_links":[],"categories":["Recently Updated","Testing"],"sub_categories":["[Aug 31, 2024](/content/2024/08/31/README.md)"],"readme":"# Efx\n\n![Tests](https://github.com/bravobike/efx/actions/workflows/main.yaml/badge.svg)\n[![Hex version badge](https://img.shields.io/hexpm/v/efx.svg)](https://hex.pm/packages/efx)\n\n\nTesting with side-effects is often hard. Various solutions exist to work around\nthe difficulties, e.g. mocking. This library offers a very easy way to achieve\ntestable code by mocking. Instead of mocking we talk about binding effects to another implementation.\n`Efx` offers a declarative way to mark effectful functions and bind them in tests.\n\nEfx allows async testing even in with child-processes, since it uses process-dictionaries\nto store bindings and find them in the supervision-tree (see this [test-case](https://github.com/bravobike/efx/blob/ffe213db51d1b55cf81dd492170d9785284f54c4/test/efx_case_test.exs#L52)).\n\n## Rationale\n\nEfx is a small library that does one thing and one thing only very well: Make code\nthat contains side effects testable.\n\nExisting mock libraries often set up mocks in non-declarative ways: configs need\nto be adapted \u0026 mock need to be initialized. In source code there are intrusive\ninstructions to set up mockable code. `Efx` is very unintrusive in both, source\ncode and test code. It offers a convenient and declarative syntax. Instead of\nmocking we talk about binding effects.\n\nEfx follows the following principles:\n\n- Implementing and binding effects should be as simple and declarative as possible.\n- Modules contain groups of effects that can only be bound as a set.\n- We want to run as many tests async as possible. Thus, we traverse\n  the supervision tree to find rebound effects in the spawning test processes,\n  in an isolated manner.\n- Effects by default execute their default implementation in tests, and thus, must be explicitly bound.\n- Effects can only be bound in tests, but not in production. In production, the default implementation is always executed.\n- We want zero performance overhead in production.\n\n\n## Usage\n\n### Setup\n\nTo use `Efx` in your project, add this to your dependencies in `mix.ex`:\n\n```elixir\n{:efx, \"~\u003e 1.0.1\"}\n```\n\nIf you want to have proper formatting of the `Efx.defeffect/2` macro, you can add\nthe following line to your `.formatter.ex`:\n\n```elixir\n[\n  ...,\n  import_deps: [:efx]\n]\n```\n\n### Example\n\nGiven the following code:\n\n```elixir\ndefmodule MyModule do\n\n  def read_data() do\n    File.read!(\"file.txt\")\n    |\u003e deserialize()\n  end\n\n  def write_data(data) do\n    serialized_data = data |\u003e serialize()\n    File.write!(\"file.txt\", deserialized_data)\n  end\n\n  defp deserialize(raw) do\n    ...\n  end\n\n  defp serialize(data) do\n    ...\n  end\n\nend\n```\n\nIn this example, it's quite complicated to test deserialization and serialization since\nwe have to prepare and place the file correctly for each test.\n\nWe can rewrite the module using `Efx` as follows:\n\n\n```elixir\ndefmodule MyModule do\n\n  use Efx\n\n  def read_data() do\n    read_file!()\n    |\u003e deserialize()\n  end\n\n  def write_data(data) do\n    data\n    |\u003e serialize()\n    |\u003e write_file!()\n  end\n\n  @spec read_file!() :: binary()\n  defeffect read_file!() do\n    File.read!(\"file.txt\")\n  end\n\n  @spec write_file!(binary()) :: :ok\n  defeffect write_file!(raw) do\n    File.write!(\"file.txt\", raw)\n  end\n\n  ...\n\nend\n```\n\nBy using the `defeffect`-macro, we define an effect-function as well as provide\na default-implementation in its body. It is mandatory for each of the effect-functions to have a matching spec.\n\nThe above code is now easily testable since we can rebind the effect-functions with ease:\n\n```elixir\ndefmodule MyModuleTest do\n\n  use EfxCase\n\n  describe \"read_data/0\" do\n    test \"works as expected with empty file\" do\n      bind(\u0026MyModule.read_file!/0, fn -\u003e \"\" end)\n      bind(\u0026MyModule.write_file!/1, fn _ -\u003e :ok end)\n\n      # test code here\n      ...\n    end\n\n    test \"works as expected with proper contents\" do\n      bind(\u0026MyModule.read_file!/0, fn -\u003e \"some expected file content\" end)\n      bind(\u0026MyModule.write_file!/1, fn _ -\u003e :ok end)\n\n      # test code here\n      ...\n    end\n\n  end\n\nend\n```\n\nInstead of returning the value of the default implementation, `MyModule.read_file!/0` returns test data that is needed for the test case. `MyModule.write_file!` does nothing.\n\nFor more details, see the `EfxCase`-module.\n\n### Caution: Efx generates a behaviour\n\nNote that Efx generates and implements a behavior. Thus, it is recommended, to move side effects to a dedicated submodule, to not accidentally interfere with existing behaviors.\nThat said, we create the following module:\n\n\n```elixir\ndefmodule MyModule.Effects do\n\n  use Efx\n\n  @spec read_file!() :: binary()\n  defeffect read_file!() do\n    File.read!(\"file.txt\")\n  end\n\n  @spec write_file!(binary()) :: :ok\n  defeffect write_file!(raw) do\n    File.write!(\"file.txt\", raw)\n  end\n\nend\n```\n\nand straight forward use it in the original module:\n\n```elixir\ndefmodule MyModule do\n\n  alias MyModule.Effects\n\n  def read_data() do\n    Effects.read_file!()\n    |\u003e deserialize()\n  end\n\n  def write_data(data) do\n    data\n    |\u003e serialize()\n    |\u003e Effects.write_file!()\n  end\n\n  ...\nend\n```\n\nThat way, we achieve a clear separation between effectful and pure code.\n\n### Delegate Effects\n\nThe same way we use `Kernel.defdelegate/2` we can implement effect functions that just delegate to another function like so: \n\n```elixir\n@spec to_atom(String.t()) :: atom()\ndelegateeffect to_atom(str), to: String\n```\n\n`delegateeffect` follows the same syntax as `Kernel.defdelegate/2`.\nFunctions defined using `defdelegate` are bindable in tests like they were created using `defeffect`.\n\n### Custom Test Environments\n\nPer default, binding only works in mix-env `:test`. In other environments, effect functions are stripped to their default function body to not cause runtime-overhead. If you need bindings in another test environment, you can add the following to your config:\n\n```\nconfig :efx, :test_envs, [:test, :other_test_env]\n```\n\nNote that you then need to explicitly list `:test` as well.\n\n\n## OTP Version 25 required\n\nThe ancestor-key in process dictionaries is relativly new to Erlang. It was introduced with OTP 25 and, thus, this is the minimal required OTP-version.\n\n## License\nCopyright © 2024 Bravobike GmbH and Contributors\n\nThis project is licensed under the Apache 2.0 license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbravobike%2Fefx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbravobike%2Fefx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbravobike%2Fefx/lists"}