Skip to content
/ efx Public
forked from bravobike/efx

A library to declaratively write testable effects

License

Notifications You must be signed in to change notification settings

sodapopcan/efx

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Efx

Tests Hex version badge

Testing with side-effects is often hard. Various solutions exist to work around the difficulties, e.g. mocking. This library offers a very easy way to achieve testable code by mocking. Instead of mocking we talk about binding effects to another implementation. Efx offers a declarative way to mark effectful functions and bind them in tests.

Efx allows async testing even in with child-processes, since it uses process-dictionaries to store bindings and find them in the super vision tree.

Rationale

Efx is a small library that does one thing and one thing only very well: Make code that contains side effects testable.

Existing mock libraries often set up mocks in non declarative ways: configs need to be adapted & mock need to be initialized. In source code there are intrusive instructions to set up mockable code. Efx is very unintrusive in both, source code and test code. It offers a convenient and declarative syntax. Instead of mocking we talk about binding effects.

Efx follows the following principles:

  • Implementing and binding effects should be as simple and declarative as possible.
  • Modules contain groups of effects that can only be bound as a set.
  • We want to run as much tests async as possible. Thus, we traverse the supervision tree to find rebound effects in the ancest test processes, in an isolated manner.
  • Effects by default execute their default implemenation in tests, and thus, must be explicitly bound.
  • Effects can only be bound in tests, but not in production. In production always the default implementation is executed.
  • We want zero performance overhead in production.

Usage

Defining Effects

To define effects we insert the use-Macro provided by the Efx-Module as follows:

defmodule MyEffect do
  use Efx

  @spec read_numbers(String.t()) :: integer()
  defeffect read_numbers(id) do
    ... 
  end

  @spec write_numbers(String.t(), integer()) :: :ok
  defeffect write_numbers(id, numbers) do
    ...
  end
end

By using the defeffect-macro, we define an effect-function as well as provide a default-implementation in its body. For more detail see the moduledoc in the Efx-module.

Binding Effects in Tests

To bind effects one simply has to use EfxCase-Module and call bind functions. Lets say we have the following effects implementation:

defmodule MyModule do
  use Efx 

  @spec get() :: list()
  defeffect get() do
     ...
  end
end

The following shows code that binds the effect to a different implementation in tests:

defmodule SomeTest do
  use EfxCase

  test "test something" do
    bind(MyModule, :get, fn -> [1,2,3] end)
    ...
  end
end

Instead of returning the value of the default implementation, MyModule.get/0 returns [1,2,3].

For more details see the EfxCase-module.

License

Copyright © 2024 Bravobike GmbH and Contributors

This project is licensed under the Apache 2.0 license.

About

A library to declaratively write testable effects

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Elixir 100.0%