Dependency injection and unit testing

Hi everyone!

I’m looking for the best solution to stub dependencies for proper unit testing.

Let’s assume we have a module which is responsible for user authentication. In order to pass incoming connection a received signature should be validated. I’d like to stub actual validation procedure for testing purposes. The module might look like this:

defmodule Auth do
  def verify(sign) do
    if Validator.check(sign) do
        :ok
    else
        :error
    end
  end
end

The question is how to stub Validator.check call?

Here are solutions I’ve found so far.

Control dependencies via application configuration.

defmodule Auth do
  @validator_module Application.get_env(:my_app, :validator)

  def verify(sign) do
    if @validator_module.check(sign) do
        :ok
    else
        :error
    end
  end
end

This way both production and testing environments contain hundreds of lines like:

config :my_app, validator: Validator

for production and

config :my_app, validator: ValidatorStub

for test environment.

Besides if I’d like to build a kind of integration test (when not all dependencies are stubbed) I need a custom configuration for this. It looks really boring.

Use mocking libraries like jjh42/mock

defmodule AuthTest do
  use ExUnit.Case
  import Mock

  test "the module works fine" do
    with_mock(Validator, [check: fn (_sign) -> true end] do
      Auth.verify("any_sign")
    end
  end
end

It actually works but looks like a hack and forces you to write tons of boilerplate code. Elixir is not for stubbing right?

Inheritance and defoverridable

From the Brian’s topic I posted above I thought up the next implementation.

defmodule AuthBase do
  defmacro __using__ do
    quote do
      def check(sign) do
        Validator.check(sign)
      end
      defoverridable [check: 1] 

      def verify(sign) do
        if check(sign) do
          :ok
        else
          :error
        end
      end
    end
  end
end
defmodule Auth do
  use AuthBase
end
defmodule AuthStub do
  use AuthBase

  def check(sign), do: true
end

This way we always should place the whole implementation into macro. It may work but looks weird.

Present any dependency as a standalone process (GenServer)

defmodule Validator do
  use GenServer

  def start_link(args), do: GenServer.start_link(__MODULE__, args, name: __MODULE__)
  
  def init(args), do: {:ok, args}

  def check(sign) do
    GenServer.call(__MODULE__, {:check, sign})
  end

  def handle_call({:check, sign}, _from, _state) do
    # do validation
  end
end
defmodule ValidatorStub do
  use GenServer

  def start_link(args), do: GenServer.start_link(Validator, args, name: Validator)
  
  def init(args), do: {:ok, args}

  def check(sign) do
    GenServer.call(Validator, {:check, sign})
  end

  def handle_call({:check, sign}, _from, state) do
    # send(_from.pid, {:check, sign}) and then assert_receive if you'd like 
    {:reply, true, state}
  end
end

Production environment supervisor has Validator as a child meanwhile a test environment should call

start_supervised(ValidatorStub)

before testing Auth module.

As for me this is the most convenient way to mock dependencies however I’m worry about performance costs (GenServer calls overhead). Moreover it seems quite strange to make every module in the system GenServer.

Conclusion

I tried to use every concept and I remain unsatisfied. Does anyone know how to do this properly and not to reinvent the wheel? :slight_smile:

You might want to check this excellent article Mocks and explicit contracts by José Valim and the Mox library mentioned there.

@mudasobwa thanks for your reply!

If I’m not mistaken this article inspired me to produce the first solution I described above (I omit behaviour declaration for simplicity).

By the way I totally forgot about the most obvious solution mentioned there - passing dependency as an argument. :slight_smile:

defmodule Auth do
  def verify(sign, validator \\ Validator) do
    if validator.check(sign) do
        :ok
    else
        :error
    end
  end
end
1 Like