Current section
Files
Jump to
Current section
Files
lib/delx.ex
defmodule Delx do
@moduledoc """
An Elixir library to make function delegation testable.
## Usage
Let's say you have the following module.
iex> 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 `defdel/2` macro in the module body. It has
the same API as Elixir's own `Kernel.defdelegate/2` macro.
iex> defmodule Greeter do
...> use Delx, otp_app: :greeter
...> defdel hello(name), to: Greeter.StringGreeter
...> end
iex> Greeter.hello("Tobi")
"Hello, Tobi!"
## Testing
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.
### Built-In Assertions
Delx brings it's own test assertions.
All you need to do is to activate delegation stubbing for your test
environment by putting the following line in your `config/test.exs`:
config :greeter, Delx, stub: true
Then in your tests, you can import `Delx.TestAssertions` and use the
`Delx.TestAssertions.assert_delegate/2` and
`Delx.TestAssertions.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 stubbing all delegated functions do not return
anymore but instead raise the `Delx.StubbedDelegationError`. If you really
want to call the original implementation, you have to avoid any calls of
delegated functions.
### With Mox
If you are using [Mox](https://hexdocs.pm/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(Delx.Delegator.Mock, 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: Delx.Delegator.Mock
Please make sure not to use the `:stub` 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(
Delx.Delegator.Mock,
: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.
"""
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
otp_app =
opts[:otp_app] ||
raise ArgumentError, "expected otp_app: to be given as argument"
@doc false
@spec __delegator__() :: module
def __delegator__ do
config = Application.get_env(unquote(otp_app), Delx, [])
case Keyword.fetch(config, :stub) do
{:ok, true} -> Delx.Delegator.Stub
_ -> Keyword.get(config, :delegator, Delx.Delegator.Common)
end
end
import Delx.Defdel
end
end
end