klajo / mockgyver

A mocking library for Erlang
BSD 3-Clause "New" or "Revised" License
38 stars 13 forks source link

mockgyver

Hex pm Build Status Erlang Versions

mockgyver is an Erlang tool which will make it easier to write EUnit tests which need to replace or alter (stub/mock) the behaviour of other modules.

mockgyver aims to make that process as easy as possible with a readable and concise syntax.

mockgyver is built around two main constructs: ?WHEN which makes it possible to alter the behaviour of a function and another set of macros (like ?WAS_CALLED) which check that a function was called with a chosen set of arguments.

Read more about constructs and syntax in the documentation for the mockgyver module.

The documentation is generated using the edown extension which generates documentation which is immediately readable on github. Remove the edown lines from rebar.config to generate regular edoc.

A quick tutorial

Let's assume we want to make sure a fictional program sets up an ssh connection correctly (in order to test the part of our program which calls ssh:connect/3) without having to start an ssh server. Then we can use the ?WHEN macro to replace the original ssh module and let connect/3 return a bogus ssh_connection_ref():

    ?WHEN(ssh:connect(_Host, _Port, _Opts) -> {ok, ssh_ref}),

Also, let's mock close/1 while we're at it to make sure it won't crash on the bogus ssh_ref:

    ?WHEN(ssh:close(_ConnRef) -> ok),

When testing our program, we want to make sure it calls the ssh module with the correct arguments so we'll add these lines:

    ?WAS_CALLED(ssh:connect({127,0,0,1}, 2022, [])),
    ?WAS_CALLED(ssh:close(ssh_ref)),

For all of this to work, the test needs to be encapsulated within either the ?MOCK macro or the ?WITH_MOCKED_SETUP (recommended for eunit). Assume the test case above is within a function called sets_up_and_tears_down_ssh_connection:

    sets_up_and_tears_down_ssh_connection_test() ->
        ?MOCK(fun sets_up_and_tears_down_ssh_connection/0).

Or, if you prefer ?WITH_MOCKED_SETUP:

    ssh_test_() ->
        ?WITH_MOCKED_SETUP(fun setup/0, fun cleanup/1).

    sets_up_and_tears_down_ssh_connection_test(_) ->
        ...

Sometimes a test requires a process to be started before a test, and stopped after a test. In that case, the latter is better (it'll automatically export and call all ...test/1 functions).

The final test case could look something like this:

    -include_lib("mockgyver/include/mockgyver.hrl").

    ssh_test_() ->
        ?WITH_MOCKED_SETUP(fun setup/0, fun cleanup/1).

    setup() ->
        ok.

    cleanup(_) ->
        ok.

    sets_up_and_tears_down_ssh_connection_test(_) ->
        ?WHEN(ssh:connect(_Host, _Port, _Opts) -> {ok, ssh_ref}),
        ?WHEN(ssh:close(_ConnRef) -> ok),
        ...start the program and trigger the ssh connection to open...
        ?WAS_CALLED(ssh:connect({127,0,0,1}, 2022, [])),
        ...trigger the ssh connection to close...
        ?WAS_CALLED(ssh:close(ssh_ref)),

API documentation

See mockgyver for details.

Caveats

There are some pitfalls in using mockgyver that you might want to know about.

History

It all started when a friend of mine (Tomas Abrahamsson) and I (Klas Johansson) wrote a tool we called the stubber. Using it looked something like this:

    stubber:run_with_replaced_modules(
        [{math, pi, fun() -> 4 end},
         {some_module, some_function, fun(X, Y) -> ... end},
         ...],
        fun() ->
            code which should be run with replacements above
        end),

Time went by and we had test cases which needed a more intricate behaviour, the stubs grew more and more complicated and that's when I thought: it must be possible to come up with something better and that's when I wrote mockgyver.

Using mockgyver with your own application

mockgyver is available as a hex package. Just add it as a dependency to your rebar.config:

{deps, [mockgyver]}.

... or with a specific version:

{deps, [{mockgyver, "some.good.version"}]}.

Building

Build mockgyver using rebar. Also, parse_trans (for the special syntax), edown (for docs on github) and eunit_addons (for ?WITH_MOCKED_SETUP) are required, but rebar takes care of that.

$ git clone git://github.com/klajo/mockgyver.git
$ rebar3 compile

Build docs using the edown library (for markdown format):

$ rebar3 as edown edoc

Build regular docs:

$ rebar3 edoc