• Stars
    star
    812
  • Rank 56,150 (Top 2 %)
  • Language
    Erlang
  • License
    Apache License 2.0
  • Created over 14 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A mocking library for Erlang

Meck

A mocking library for Erlang

GitHub Actions Hex.pm version Hex.pm license Erlang versions hex.pm license

Features

See what's new in 0.8 Release Notes.

  • Dynamic return values using sequences and loops of static values
  • Compact definition of mock arguments, clauses and return values
  • Pass through: call functions in the original module
  • Complete call history showing calls, return values and exceptions
  • Mock validation, will invalidate mocks that were not called correctly
  • Throwing of expected exceptions that keeps the module valid
  • Throws an error when mocking a module that doesn't exist or has been renamed (disable with option non_strict)
  • Support for Hamcrest matchers
  • Automatic backup and restore of cover data
  • Mock is linked to the creating process and will unload automatically when a crash occurs (disable with option no_link)
  • Mocking of sticky modules (using the option unstick)

Examples

Here's an example of using Meck in the Erlang shell:

Eshell V5.8.4  (abort with ^G)
1> meck:new(dog, [non_strict]). % non_strict is used to create modules that don't exist
ok
2> meck:expect(dog, bark, fun() -> "Woof!" end).
ok
3> dog:bark().
"Woof!"
4> meck:validate(dog).
true
5> meck:unload(dog).
ok
6> dog:bark().
** exception error: undefined function dog:bark/0

Exceptions can be anticipated by Meck (resulting in validation still passing). This is intended to be used to test code that can and should handle certain exceptions indeed does take care of them:

5> meck:expect(dog, meow, fun() -> meck:exception(error, not_a_cat) end).
ok
6> catch dog:meow().
{'EXIT',{not_a_cat,[{meck,exception,2},
                    {meck,exec,4},
                    {dog,meow,[]},
                    {erl_eval,do_apply,5},
                    {erl_eval,expr,5},
                    {shell,exprs,6},
                    {shell,eval_exprs,6},
                    {shell,eval_loop,3}]}}
7> meck:validate(dog).
true

Normal Erlang exceptions result in a failed validation. The following example is just to demonstrate the behavior, in real test code the exception would normally come from the code under test (which should, if not expected, invalidate the mocked module):

8> meck:expect(dog, jump, fun(Height) when Height > 3 ->
                                  erlang:error(too_high);
                             (Height) ->
                                  ok
                          end).
ok
9> dog:jump(2).
ok
10> catch dog:jump(5).
{'EXIT',{too_high,[{meck,exec,4},
                   {dog,jump,[5]},
                   {erl_eval,do_apply,5},
                   {erl_eval,expr,5},
                   {shell,exprs,6},
                   {shell,eval_exprs,6},
                   {shell,eval_loop,3}]}}
11> meck:validate(dog).
false

Here's an example of using Meck inside an EUnit test case:

my_test() ->
    meck:new(my_library_module),
    meck:expect(my_library_module, fib, fun(8) -> 21 end),
    ?assertEqual(21, code_under_test:run(fib, 8)), % Uses my_library_module
    ?assert(meck:validate(my_library_module)),
    meck:unload(my_library_module).

Pass-through is used when the original functionality of a module should be kept. When the option passthrough is used when calling new/2 all functions in the original module will be kept in the mock. These can later be overridden by calling expect/3 or expect/4.

Eshell V5.8.4  (abort with ^G)
1> meck:new(string, [unstick, passthrough]).
ok
2> string:strip("  test  ").
"test"

It's also possible to pass calls to the original function allowing us to override only a certain behavior of a function (this usage is compatible with the passthrough option). passthrough/1 will always call the original function with the same name as the expect is defined in):

Eshell V5.8.4  (abort with ^G)
1> meck:new(string, [unstick, passthrough]).
ok
2> meck:expect(string, strip, fun
    ("foo") -> "bar";
    (String) -> meck:passthrough([String])
end).
ok
3> string:strip("  test  ").
"test"
4> string:strip("foo").
"bar"
5> meck:unload(string).
ok
5> string:strip("foo").
"foo"

Use

Meck is best used via Rebar 3. Add Meck to the test dependencies in your rebar.config:

{profiles, [{test, [{deps, [meck]}]}]}.

Manual Build

Meck uses Rebar 3. To build Meck go to the Meck directory and simply type:

rebar3 compile

In order to run all tests for Meck type the following command from the same directory:

rebar3 eunit

Documentation can be generated through the use of the following command:

rebar3 edoc

Test Output

Normally the test output is hidden, but if EUnit is run directly, two things might seem alarming when running the tests:

  1. Warnings emitted by cover
  2. An exception printed by SASL

Both are expected due to the way Erlang currently prints errors. The important line you should look for is All XX tests passed, if that appears all is correct.

Caveats

Global Namespace

Meck will have trouble mocking certain modules since it works by recompiling and reloading modules in the global Erlang module namespace. Replacing a module affects the whole Erlang VM and any running processes using that module. This means certain modules cannot be mocked or will cause trouble.

In general, if a module is used by running processes or include Native Implemented Functions (NIFs) they will be hard or impossible to mock. You may be lucky and it could work, until it breaks one day.

The following is a non-exhaustive list of modules that can either be problematic to mock or not possible at all:

  • erlang
  • supervisor
  • All gen_ family of modules (gen_server, gen_statem etc.)
  • os
  • crypto
  • compile
  • global
  • timer (possible to mock, but used by some test frameworks, like Elixir's ExUnit)

Local Functions

A meck expectation set up for a function f does not apply to the module- local invocation of f within the mocked module. Consider the following module:

-module(test).
-export([a/0, b/0, c/0]).

a() ->
  c().

b() ->
  ?MODULE:c().

c() ->
  original.

Note how the module-local call to c/0 in a/0 stays unchanged even though the expectation changes the externally visible behaviour of c/0:

3> meck:new(test, [passthrough]).
ok
4> meck:expect(test,c,0,changed).
ok
5> test:a().
original
6> test:b().
changed
6> test:c().
changed

Common Test

When using meck under Erlang/OTP's Common Test, one should pay special attention to this bit in the chapter on Writing Tests:

init_per_suite and end_per_suite execute on dedicated Erlang processes, just like the test cases do.

Common Test runs init_per_suite in an isolated process which terminates when done, before the test case runs. A mock that is created there will also terminate and unload itself before the test case runs. This is because it is linked to the process creating it. This can be especially tricky to detect if passthrough is used when creating the mock, since it is hard to know if it is the mock responding to function calls or the original module.

To avoid this, you can pass the no_link flag to meck:new/2 which will unlink the mock from the process that created it. When using no_link you should make sure that meck:unload/1 is called properly (for all test outcomes, or crashes) so that a left-over mock does not interfere with subsequent test cases.

Contribute

Patches are greatly appreciated! For a much nicer history, please write good commit messages. Use a branch name prefixed by feature/ (e.g. feature/my_example_branch) for easier integration when developing new features or fixes for meck.

Should you find yourself using Meck and have issues, comments or feedback please create an issue here on GitHub.

Meck has been greatly improved by many contributors!

Donations

If you or your company use Meck and find it useful, a sponsorship or donations are greatly appreciated!

Sponsor on GitHub Donate using Liberapay

More Repositories

1

grapherl

Create graphs of Erlang systems and programs
Erlang
89
star
2

unite

Pretty EUnit test formatters
Erlang
71
star
3

tap

Recon wrapper for Elixir
Elixir
63
star
4

pretty_errors

Error and stack trace pretty printers for Erlang
Erlang
38
star
5

parallel

A parallel Enum implementation for Elixir
Elixir
35
star
6

future

Futures for Elixir
Elixir
22
star
7

stout

Stylized output for Lager
Erlang
21
star
8

mapz

Additions to the Erlang maps module
Erlang
14
star
9

emacs-iedit

Edit multiple regions with the same content simultaneously
Emacs Lisp
11
star
10

color

Shell ANSI coloring for erlang
Erlang
11
star
11

grid

Erlang formatter for tabular data
Erlang
11
star
12

kraft

A minimalistic Erlang web framework
Erlang
11
star
13

asdf-kerl

[OBSOLETE] Kerl-based Erlang plugin for asdf version manager https://github.com/asdf-vm/asdf
Shell
10
star
14

opsworks

Command line interface for Amazon OpsWorks
Ruby
6
star
15

focus_last_tab

Always focus the last tab with Ctrl+9 or ⌘+9 in Sublime Text
Python
5
star
16

elementary

A modern S3 client for Erlang
Erlang
5
star
17

pad.zsh-theme

A concise and colorful ZSH theme
4
star
18

fakes3-docker

Minimal fakes3 Docker image
3
star
19

stache

Mustache Template Language for Erlang
Erlang
3
star
20

subtree

Erlang library for working with nested data structures
Erlang
3
star
21

erlang-base

Minimal Erlang + Debian Docker image
3
star
22

ansi

ANSI escape code library for Erlang
Erlang
3
star
23

emacs-config

My Emacs configuration
Emacs Lisp
2
star
24

textual

Textual transformation library for Erlang
Erlang
2
star
25

keysmith

A Erlang library for generating unique identifiers
Erlang
1
star
26

scripts

Various scripts and tools for the everyday developer!
1
star
27

entitas

This repository has moved to
Go
1
star
28

rebar3-umbrella-example

Erlang
1
star