Delx v3.0.0 Delx View Source

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 defdelegate/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

...>   defdelegate hello(name), to: Greeter.StringGreeter
...> end

iex> Greeter.hello("Tobi")
"Hello, Tobi!"

The reason you need to use Delx and define the :otp_app option is that each application can configure their own delegation behavior. So third-party libaries that also use Delx remain unaffected of your application-specific configuration.

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

With Mox

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.