Home

Awesome

Mockompare

This repository serves as a companion project for Patch.

Purpose

The purpose of this repository is to compare functionality between the following projects.

Expectations

Patch was written with a very simple concept of how a mock is meant to work. When a test author mocks a function, public or private, they wish to replace that function with the mock for all callers.

Consider this example to see how the various libraries implement mocking.

defmodule Example do
  def public_caller(a) do
    {:ok, public_function(a)}
  end

  def public_function(a) do
    {:public, a}
  end

  def private_caller(a) do
    {:ok, private_function(a)}
  end

  ## Private

  def private_function(a) do
    {:private, a}
  end
end

With this example we can then define 4 logical scenarios to see how the various libraries behave against our expectation that a mocked function should return the mock value to all callers.

Scenario 1 - Mocking and calling a public function

In this scenario, mock public_function/1 to return the value :patched then call public_function/1.

The test author using the above definition of mocks would expect that calling public_function/1 in a test should return :patched.

LibraryResult
meck:patched
Mock:patched
Mockery:patched
Mox:patched
Patch:patched

This scenario is pretty straightforward and all the libraries produce the expected result.

Scenario 2 - Mocking a public function and calling it as a collaborator

The public_caller/1 function calls public_function/1, in testing terms we would say that public_function/1 is a collaborator.

In this scenario, mock public_function/1 to return the value :patched and then call public_caller/1 with the argument :test.

The test author using the above definition of mocks would expect that calling public_caller/1 would call public_function/1 which is mocked and so public_function/1 should return :patched and public_caller/1 will wrap :patched with an :ok tuple returning {:ok, :patched}

LibraryResult
meck{:ok, {:public, :test}}
Mock{:ok, {:public, :test}}
Mockery{:ok, {:public, :test}}
Mox{:ok, {:public, :test}}
Patch{:ok, :patched}

In this scenario Patch is the only library where the collaborator's mock is respected. The reason for this is that the call in the module is a "local call" in BEAM parlance. The other mocking libraries local calls end up in the original module and not in the mock. This result is surprising for the definition of a mocked function as returning the mock value to all callers.

Scenario 3 - Mocking a private function

In this scenario, mock private_function/1 to return the value :patched and then call private_function/1.

The test author using the above definition of mocks would expect that calling private_function/1 would return :patched

LibraryResult
meckErlangError: {:undefined_function, {Example, :private_function, 1}}
MockErlangError: {:undefined_function, {Example, :private_function, 1}}
MockeryUndefinedFunctionError
MoxArgumentError
Patch:patched

In this scenario only Patch provides the functionality to test a private function via it's expose/2 function. No other library provides an affordance for testing a private function so depending on their implementation they raise some kind of error.

Scenario 4 - Mocking a private function and calling it as a collaborator

The private_caller/1 function is a public function that calls private_function/1, in testing terms we would say that private_function/1 is a collaborator.

In this scenario, mock private_function/1 to return the value :patched and then call private_caller/1 with the argument :test

The test author using the above definition of mocks would expect that calling private_caller/1 would call private_function/1 which is mocked and so private_function/1 should return :patched and private_caller/1 will wrap :patched with an :ok tuple and return {:ok, :patched}

LibraryResult
meckErlangError: {:undefined_function, {Example, :private_function, 1}}
MockErlangError: {:undefined_function, {Example, :private_function, 1}}
MockeryUndefinedFunctionError
MoxArgumentError
Patch{:ok, :patched}

In this scenario Patch's ability to patch private functions combined with collaborator mocks being respected result in the mock being respected.

Which Library is Right?

How mocks should behave is a subjective question, there's no objective definition for which model is correct.

Patch adopts a simple but powerful definition that is consistent and easy to reason about, patched functions always act like patched functions.

The other libraries adopt a different definition of mock, one that is more in line with the underlying mechanisms of BEAM.

Reporting Issues

This comparison was assembled by following the documentation for each project to the best of my ability, but that's no guarantee that each test is implemented correctly. If an issue in the tests is found, an issue or pull request is welcome.