View Source EfxCase (Efx v0.1.8)
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 Efx
@spec get() :: list()
defeffect get() do
...
end
end
The following shows code that binds the effect:
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]
.
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 EfxCase
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 EfxCase, 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 EfxCase
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 EfxCase
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 EfxCase
@spec get() :: list()
defeffect get() do
...
end
@spec put(any()) :: :ok
defeffect put(any()) do
...
end
end
defmodule SomeTest do
use EfxCase
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.