Defdelegate on steroids! An Elixir library to make function delegation testable.
- Erlang 20 or greater
- Elixir 1.8 or greater
If available in Hex, the package can be installed
by adding delx
to your list of dependencies in mix.exs
:
def deps do
[
{:delx, "~> 3.0"}
]
end
Check out the full docs at https://hexdocs.pm/delx.
Let's say you have the following module.
defmodule Greeter.StringGreeter do
def hello(name) do
"Hello, #{name}!"
end
end
You can delegate functions calls to another module by using the Delx
module
and calling the defdelegate/2
macro in the module body. It has the same API as
Elixir's own Kernel.defdelegate/2
macro.
defmodule Greeter do
use Delx, otp_app: :greeter
defdelegate hello(name), to: Greeter.StringGreeter
end
Greeter.hello("Tobi")
# => "Hello, Tobi!"
One great benefit of Delx is that you can test delegation without invoking the actual implementation of the delegation target, thus eliminating all side effects.
Delx brings it's own test assertions.
All you need to do is to activate delegation mocking for your test environment
by putting the following line in your config/test.exs
:
config :greeter, Delx, mock: true
Then in your tests, you can import Delx.TestAssertions
and use the
assert_delegate/2
and refute_delegate/2
assertions.
defmodule GreeterTest do
use ExUnit.Case
import Delx.TestAssertions
describe "hello/1" do
test "delegate to Greeter.StringGreeter" do
assert_delegate {Greeter, :hello, 1}, to: Greeter.StringGreeter
end
end
end
Note that once you activate mocking all delegated functions do not return
anymore but instead raise the Delx.MockedDelegationError
. If you really
want to call the original implementation, you have to avoid any calls of
delegated functions.
If you are using Mox in your application you have another possibility to test delegated functions.
Register a mock for the Delx.Delegator
behavior to your
test/test_helper.exs
(or wherever you define your mocks):
Mox.defmock(Greeter.DelegatorMock, for: Delx.Delegator)
Then, in your config/test.exs
you have to set the mock as delegator module
for your app.
config :my_app, Delx, delegator: Greeter.DelegatorMock
Please make sure not to use the :mock
option and a :delegator
option at the
same time as this may lead to unexpected behavior.
Now you are able to expect
calls to delegated functions:
defmodule GreeterTest do
use ExUnit.Case
import Mox
setup :verify_on_exit!
describe "hello/1" do
test "delegate to Greeter.StringGreeter" do
expect(
Greeter.DelegatorMock,
:apply,
fn {Greeter, :hello},
{Greeter.StringGreeter, :hello},
["Tobi"] ->
:ok
end
)
Greeter.hello("Tobi")
end
end
end
For more information on how to implement your own delegator, refer to the
docs of the Delx.Delegator
behavior.
Note that the configuration is only applied at compile time, so you are unable to mock or replace the delegator module at runtime.
Note that the function defdel/2
has been removed in favor of defdelegate/2
to give a better experience with the tooling. Additionally, let's say you want
to migrate away from Delx you can simply unuse
Delx to fall back to the
default Kernel.defdelegate/2
and your code will still work.
The config option to enable testing mode has changed from stub
to mock
. If
you got the following line in your config:
config :greeter, Delx, stub: true
Please change it to:
config :greeter, Delx, mock: true