Hammox (Hammox v1.0.0)

View Source

Hammox is a library for rigorous unit testing using mocks, explicit behaviours and contract tests.

See the README page for usage guide and examples.

Most of the functions in this module come from Mox for backwards compatibility. As of v0.1.0, the only Hammox-specific functions are protect/2 and protect/3.

Summary

Types

function_arity_pair()

@type function_arity_pair() :: {atom(), arity() | [arity()]}

Functions

allow(mock, owner_pid, allowed_via)

See Mox.allow/3.

defmock(name, options)

See Mox.defmock/2.

deny(mock, function_name, arity)

See Mox.deny/3.

expect(mock, function_name, n \\ 1, code)

See Mox.expect/4.

protect(module)

(since 0.1.0)
@spec protect(module :: module()) :: %{required(atom()) => fun()}
@spec protect(mfa :: mfa()) :: fun()

See protect/3.

protect(mfa, behaviour_name)

(since 0.1.0)
@spec protect(module :: module(), funs :: [function_arity_pair()]) :: %{
  required(atom()) => fun()
}
@spec protect(mfa :: mfa(), behaviour_name :: module()) :: fun()
@spec protect(implementation_name :: module(), behaviour_name :: module()) :: %{
  required(atom()) => fun()
}

See protect/3.

protect(module, behaviour_name, funs)

(since 0.1.0)
@spec protect(
  module :: module(),
  behaviour_name :: module(),
  funs :: [function_arity_pair()]
) :: %{required(atom()) => fun()}

Decorates functions with Hammox checks based on given behaviour.

Basic usage

When passed an MFA tuple representing the function you'd like to protect, and a behaviour containing a callback for the function, it returns a new anonymous function that raises Hammox.TypeMatchError when called incorrectly or when it returns an incorrect value.

Example:

defmodule Calculator do
  @callback add(integer(), integer()) :: integer()
end

defmodule TestCalculator do
  def add(a, b), do: a + b
end

add_2 = Hammox.protect({TestCalculator, :add, 2}, Calculator)

add_2.(1.5, 2.5) # throws Hammox.TypeMatchError

Batch usage

You can decorate all functions defined by a given behaviour by passing an implementation module and a behaviour module. Optionally, you can pass an explicit list of functions as the third argument.

The returned map is useful as the return value for a test setup callback to set test context for all tests to use.

Example:

defmodule Calculator do
  @callback add(integer(), integer()) :: integer()
  @callback add(integer(), integer(), integer()) :: integer()
  @callback add(integer(), integer(), integer(), integer()) :: integer()
  @callback multiply(integer(), integer()) :: integer()
end

defmodule TestCalculator do
  def add(a, b), do: a + b
  def add(a, b, c), do: a + b + c
  def add(a, b, c, d), do: a + b + c + d
  def multiply(a, b), do: a * b
end

%{
  add_2: add_2,
  add_3: add_3,
  add_4: add_4
  multiply_2: multiply_2
} = Hammox.protect(TestCalculator, Calculator)

# optionally
%{
  add_2: add_2,
  add_3: add_3,
  multiply_2: multiply_2
} = Hammox.protect(TestCalculator, Calculator, add: [2, 3], multiply: 2)

Batch usage for multiple behviours

You can decorate all functions defined by any number of behaviours by passing an implementation module and a list of behaviour modules.

The returned map is useful as the return value for a test setup callback to set test context for all tests to use.

Example:

defmodule Calculator do
  @callback add(integer(), integer()) :: integer()
  @callback multiply(integer(), integer()) :: integer()
end

defmodule AdditionalCalculator do
  @callback subtract(integer(), integer()) :: integer()
end

defmodule TestCalculator do
  def add(a, b), do: a + b
  def multiply(a, b), do: a * b
  def subtract(a, b), do: a - b
end

%{
  add_2: add_2,
  multiply_2: multiply_2
  subtract_2: subtract_2
} = Hammox.protect(TestCalculator, [Calculator, AdditionalCalculator])

Behaviour-implementation shortcuts

Often, there exists one "default" implementation for a behaviour. A common practice is then to define both the callbacks and the implementations in one module. For these behaviour-implementation modules, Hammox provides shortucts that only require one module.

Example:

defmodule Calculator do
  @callback add(integer(), integer()) :: integer()
  def add(a, b), do: a + b
end

Hammox.protect({Calculator, :add, 2})
# is equivalent to
Hammox.protect({Calculator, :add, 2}, Calculator)

Hammox.protect(Calculator, add: 2)
# is equivalent to
Hammox.protect(Calculator, Calculator, add: 2)

Hammox.protect(Calculator)
# is equivalent to
Hammox.protect(Calculator, Calculator)

set_mox_from_context(context)

See Mox.set_mox_from_context/1.

set_mox_global(context \\ %{})

See Mox.set_mox_global/1.

set_mox_private(context \\ %{})

See Mox.set_mox_private/1.

stub(mock, function_name, code)

See Mox.stub/3.

stub_with(mock, module)

See Mox.stub_with/2.

verify!()

See Mox.verify!/0.

verify!(mock)

See Mox.verify!/1.

verify_on_exit!(context \\ %{})

See Mox.verify_on_exit!/1.