Skip to content

Commit

Permalink
Add a table of contents and slightly reorder README
Browse files Browse the repository at this point in the history
  • Loading branch information
Olshansk committed Dec 7, 2019
1 parent 3bd5ce8 commit dc5938a
Showing 1 changed file with 122 additions and 105 deletions.
227 changes: 122 additions & 105 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[![Build Status](https://travis-ci.org/jjh42/mock.svg?branch=master)](https://travis-ci.org/jjh42/mock)
[![Coverage Status](https://coveralls.io/repos/github/jjh42/mock/badge.svg?branch=master)](https://coveralls.io/github/jjh42/mock?branch=master)

# Mock
A mocking library for the Elixir language.
Expand All @@ -10,6 +9,24 @@ functionality in a convenient manner for integrating in Elixir tests.

See the full [reference documentation](https://hexdocs.pm/mock/Mock.html).

# Table of Contents
* [Mock](#Mock)
* [Installation](#Installation)
* [*with_mock* - Mocking a single module](#*with_mock*---Mocking-a-single-module)
* [*with_mocks* - Mocking multiple modules](#*with_mocks*---Mocking-multiple-modules)
* [*test_with_mock* - with_mock helper](#*test_with_mock*---with_mock-helper)
* [*setup_with_mocks* - Configure all tests to have the same mocks](#*setup_with_mocks*---Configure-all-tests-to-have-the-same-mocks)
* [Mocking input dependant output](#Mocking-input-dependant-output)
* [Mocking functions with different arities](#Mocking-functions-with-different-arities)
* [*passthrough* - partial mocking of a module](#*passthrough*---partial-mocking-of-a-module)
* [Assert called - assert a specific function was called](#Assert-called---assert-a-specific-function-was-called)
* [Assert called - specific value](#Assert-called---specific-value)
* [Assert called - wildcard](#Assert-called---wildcard)
* [NOT SUPPORTED - Mocking internal function calls](#NOT-SUPPORTED---Mocking-internal-function-calls)
* [Tips](#Tips)
* [Help](#Help)
* [Suggestions](#Suggestions)

## Installation
First, add mock to your `mix.exs` dependencies:

Expand Down Expand Up @@ -80,54 +97,6 @@ end
The second parameter of each tuple is `opts` - a list of optional arguments
passed to meck.

## Mocking input dependant output

If you have a function that should return different values depending on what the
input is, you can do as follows:

```` elixir
defmodule MyTest do
use ExUnit.Case, async: false

import Mock

test "mock functions with multiple returns" do
with_mock(Map, [
get: fn
(%{}, "http://example.com") -> "<html>Hello from example.com</html>"
(%{}, "http://example.org") -> "<html>example.org says hi</html>"
end
]) do
assert Map.get(%{}, "http://example.com") == "<html>Hello from example.com</html>"
assert Map.get(%{}, "http://example.org") == "<html>example.org says hi</html>"
end
end
end
````

## Mocking functions with different arities

You can mock functions in the same module with different arity:

```` elixir
defmodule MyTest do
use ExUnit.Case, async: false

import Mock

test "mock functions with different arity" do
with_mock String,
[slice: fn(string, range) -> string end,
slice: fn(string, range, len) -> string end]
do
assert String.slice("test", 1..3) == "test"
assert String.slice("test", 1, 3) == "test"
end
end
end

````

## *test_with_mock* - with_mock helper

An additional convenience macro `test_with_mock` is supplied which internally
Expand Down Expand Up @@ -170,24 +139,6 @@ defmodule MyTest do
end
````

## *passthrough*

By default, only the functions being mocked can be accessed from within the test.
Trying to call a non-mocked function from a mocked Module will result in an error.
This can be circumvented by passing the `:passthrough` option like so:

```` elixir
defmodule MyTest do
use ExUnit.Case, async: false
import Mock

test_with_mock "test_name", IO, [:passthrough], [] do
IO.puts "hello"
assert_called IO.puts "hello"
end
end
````

## *setup_with_mocks* - Configure all tests to have the same mocks

The `setup_with_mocks` mocks up multiple modules prior to every single test
Expand Down Expand Up @@ -224,70 +175,73 @@ are not using `async: true` in any module where you are testing.
Also, because of the way mock overrides the module, it must be defined in a
separate file from the test file.

## NOT SUPPORTED - Mocking internal function calls
## Mocking input dependant output

A common issue a lot of developers run into is Mock's lack of support for mocking
internal functions. Mock will behave as follows:
If you have a function that should return different values depending on what the
input is, you can do as follows:

```` elixir
defmodule MyApp.IndirectMod do

def value do
1
end
defmodule MyTest do
use ExUnit.Case, async: false

def indirect_value do
value()
end
import Mock

def indirect_value_2 do
MyApp.IndirectMod.value()
test "mock functions with multiple returns" do
with_mock(Map, [
get: fn
(%{}, "http://example.com") -> "<html>Hello from example.com</html>"
(%{}, "http://example.org") -> "<html>example.org says hi</html>"
end
]) do
assert Map.get(%{}, "http://example.com") == "<html>Hello from example.com</html>"
assert Map.get(%{}, "http://example.org") == "<html>example.org says hi</html>"
end
end

end
````

## Mocking functions with different arities

You can mock functions in the same module with different arity:

```` elixir
defmodule MyTest do
use ExUnit.Case, async: false

import Mock

test "indirect mock" do
with_mocks([
{ MyApp.IndirectMod, [:passthrough], [value: fn -> 2 end] },
]) do
# The following assert succeeds
assert MyApp.IndirectMod.indirect_value_2() == 2
# The following assert also succeeds
assert MyApp.IndirectMod.indirect_value() == 1
test "mock functions with different arity" do
with_mock String,
[slice: fn(string, range) -> string end,
slice: fn(string, range, len) -> string end]
do
assert String.slice("test", 1..3) == "test"
assert String.slice("test", 1, 3) == "test"
end
end
end

````

It is important to understand that only fully qualified function calls get mocked.
The reason for this is because of the way Meck is structured. Meck creates a thin wrapper module with the name of the mocked module (and passes through any calls to the original
Module in case passthrough is used). The original module is renamed, but otherwise unmodified. Once the call enters the original module, the local function call jumps stay in the module.
## *passthrough* - partial mocking of a module

Big thanks to @eproxus (author of Meck) who helped explain this to me. We're looking
into some alternatives to help solve this, but it is something to be aware of in the meantime. The issue is being tracked in [Issue 71](https://github.com/jjh42/mock/issues/71).
By default, only the functions being mocked can be accessed from within the test.
Trying to call a non-mocked function from a mocked Module will result in an error.
This can be circumvented by passing the `:passthrough` option like so:

In order to workaround this issue, the `indirect_value` can be rewritten like so:
```` elixir
def indirect_value do
__MODULE__.value()
end
````

Or, like so:
defmodule MyTest do
use ExUnit.Case, async: false
import Mock

```` elixir
def indirect_value do
MyApp.IndirectMod.value()
test_with_mock "test_name", IO, [:passthrough], [] do
IO.puts "hello"
assert_called IO.puts "hello"
end
end
````

## Assert called
## Assert called - assert a specific function was called

You can check whether or not your mocked module was called.

Expand Down Expand Up @@ -330,6 +284,69 @@ defmodule MyTest do
end
````

## NOT SUPPORTED - Mocking internal function calls

A common issue a lot of developers run into is Mock's lack of support for mocking
internal functions. Mock will behave as follows:

```` elixir
defmodule MyApp.IndirectMod do

def value do
1
end

def indirect_value do
value()
end

def indirect_value_2 do
MyApp.IndirectMod.value()
end

end
````

```` elixir
defmodule MyTest do
use ExUnit.Case, async: false
import Mock

test "indirect mock" do
with_mocks([
{ MyApp.IndirectMod, [:passthrough], [value: fn -> 2 end] },
]) do
# The following assert succeeds
assert MyApp.IndirectMod.indirect_value_2() == 2
# The following assert also succeeds
assert MyApp.IndirectMod.indirect_value() == 1
end
end
end
````

It is important to understand that only fully qualified function calls get mocked.
The reason for this is because of the way Meck is structured. Meck creates a thin wrapper module with the name of the mocked module (and passes through any calls to the original
Module in case passthrough is used). The original module is renamed, but otherwise unmodified. Once the call enters the original module, the local function call jumps stay in the module.

Big thanks to @eproxus (author of Meck) who helped explain this to me. We're looking
into some alternatives to help solve this, but it is something to be aware of in the meantime. The issue is being tracked in [Issue 71](https://github.com/jjh42/mock/issues/71).

In order to workaround this issue, the `indirect_value` can be rewritten like so:
```` elixir
def indirect_value do
__MODULE__.value()
end
````

Or, like so:

```` elixir
def indirect_value do
MyApp.IndirectMod.value()
end
````

## Tips
The use of mocking can be somewhat controversial. I personally think that it
works well for certain types of tests. Certainly, you should not overuse it. It
Expand Down

0 comments on commit dc5938a

Please sign in to comment.