View Source EfxCase (Efx v0.1.1)

Summary

Functions

Link to this macro

__using__(opts)

View Source (macro)

Module for testing with effects.

Binding effects in tests follows these principles:

  • By default, all effects of a module is bound to the default implementation.
  • We either bind all effect functions of a module or none. We cannot bind single functions (except the explicit use of :default). If we rebind only one effect and the other is called, we raise.
  • A function is either bound without or with a specified number of expected calls. If a function has multiple binds, they are called in given order, until they satisfied their expected number of calls.
  • The number of expected calls is always veryified.

Binding effects

To bind effects one simply has to use this module and call the bind macro. Lets say we have the following effects implementation:

defmodule MyModule do
  use Common.Effects 

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

The following shows code that binds the effect:

defmodule SomeTest do
  use Common.EffectsCase

  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].

Binding with an expected Number of Calls

We can additionally define an expected number of calls. The expected number of calls is always verified - a test run will fail if it is not satisfied, as well as exceeded.

We can define a number of expected calls as follows:

defmodule SomeTest do
  use Common.EffectsCase

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

In this case, we verify that the bound function get/0 is called exactly twice.

Binding globally

Effect binding uses process dictionaries to find the right binding through-out the supervision-tree. As long as calling processes have the testing process that defines the binding as an ancestor, binding works. If we cannot ensure that, we can set binding to global. However, then the tests must be set to async to not interfere:

defmodule SomeTest do
  use Common.EffectsCase, async: false

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

Binding the same Function with multiple bind-Calls

We can chain binds for the same functions. They then get executed until their number of expected calls is satisfied:

defmodule SomeTest do
  use Common.EffectsCase

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

In this example the first binding of get/0 gets called one time, then the second binding is used to replace the call two more times and the last get, specified without an expected number of calls, is used for the rest of the execution.

Setup for many Tests

If we want to setup the same binding for multiple tests we can do this as follows:

defmodule SomeTest do
  use Common.EffectsCase

  setup_effects(MyModule,
     :get, fn -> [1,2,3] end
  )

  test "test something" do
    # test with mocked get
    ...
  end
end

Explicitly defaulting one Function in Tests

While it is best practice to bind all function of a module or none, we can also default certain functions explicitly:

defmodule MyModule do
  use Common.Effects 

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

  @spec put(any()) :: :ok
  defeffect put(any()) do
     ...
  end
end


defmodule SomeTest do
  use Common.EffectsCase

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

While entirely leaving out put/1 would result in an error (when called), we can tell the effects library to use it's default implementation. Note that defaulting can be combined with an expected number of calls.

Link to this function

bind(pid, effects_behaviour, key, num \\ nil, fun)

View Source
Link to this macro

setup_effects(effects_behaviour, stubs \\ [])

View Source (macro)