| %%%============================================================================ |
| %%% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd |
| %%% |
| %%% Licensed under the Apache License, Version 2.0 (the "License"); |
| %%% you may not use this file except in compliance with the License. |
| %%% You may obtain a copy of the License at |
| %%% |
| %%% http://www.apache.org/licenses/LICENSE-2.0 |
| %%% |
| %%% Unless required by applicable law or agreed to in writing, software |
| %%% distributed under the License is distributed on an "AS IS" BASIS, |
| %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| %%% See the License for the specific language governing permissions and |
| %%% limitations under the License. |
| %%%============================================================================ |
| |
| %%% @author Adam Lindberg <eproxus@gmail.com> |
| %%% @copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd |
| %%% @doc Module mocking library for Erlang. |
| |
| -module(meck). |
| |
| %% API |
| -export_type([matcher/0]). |
| -export_type([args_spec/0]). |
| -export_type([ret_spec/0]). |
| -export_type([func_clause_spec/0]). |
| |
| %% Interface exports |
| -export([new/1]). |
| -export([new/2]). |
| -export([expect/3]). |
| -export([expect/4]). |
| -export([sequence/4]). |
| -export([loop/4]). |
| -export([delete/3]). |
| -export([delete/4]). |
| -export([expects/1]). |
| -export([expects/2]). |
| -export([exception/2]). |
| -export([passthrough/1]). |
| -export([history/1]). |
| -export([history/2]). |
| -export([validate/1]). |
| -export([unload/0]). |
| -export([unload/1]). |
| -export([called/3]). |
| -export([called/4]). |
| -export([num_calls/3]). |
| -export([num_calls/4]). |
| -export([reset/1]). |
| -export([capture/5]). |
| -export([capture/6]). |
| -export([wait/4]). |
| -export([wait/5]). |
| -export([wait/6]). |
| -export([mocked/0]). |
| |
| %% Syntactic sugar |
| -export([loop/1]). |
| -export([seq/1]). |
| -export([val/1]). |
| -export([raise/2]). |
| -export([passthrough/0]). |
| -export([exec/1]). |
| -export([is/1]). |
| |
| %%%============================================================================ |
| %%% Types |
| %%%============================================================================ |
| |
| -type meck_mfa() :: {Mod::atom(), Func::atom(), Args::[any()]}. |
| %% Module, function and arguments that the mock module got called with. |
| |
| -type stack_trace() :: [{Mod::atom(), Func::atom(), AriOrArgs::byte()|[any()]} | |
| {Mod::atom(), Func::atom(), AriOrArgs::byte()|[any()], |
| Location::[{atom(), any()}]}]. |
| %% Erlang stack trace. |
| |
| -type history() :: [{CallerPid::pid(), meck_mfa(), Result::any()} | |
| {CallerPid::pid(), meck_mfa(), Class::throw|error|exit, |
| Reason::any(), stack_trace()}]. |
| %% Represents a list of either successful function calls with a returned |
| %% result or function calls that resulted in an exception with a type, |
| %% reason and a stack trace. Each tuple begins with the pid of the process |
| %% that made the call to the function. |
| |
| -opaque matcher() :: meck_matcher:matcher(). |
| %% Matcher is an entity that is used to check that a particular value meets |
| %% some criteria. They are used in defining expectation where Erlang patterns |
| %% are not enough. E.g. to check that a numeric value is within bounds. |
| %% Instances of `matcher' can be created by {@link is/1} function from either a |
| %% predicate function or a hamcrest matcher. (see {@link is/1} for details). |
| %% An instance of this type may be specified in any or even all positions of an |
| %% {@link arg_spec()}. |
| |
| -type args_spec() :: [any() | '_' | matcher()] | non_neg_integer(). |
| %% Argument specification is used to specify argument patterns throughout Meck. |
| %% In particular it is used in definition of expectation clauses by |
| %% {@link expect/3}, {@link expect/4}, and by history digging functions |
| %% {@link num_called/3}, {@link called/3} to specify what arguments of a |
| %% function call of interest should look like. |
| %% |
| %% An argument specification can be given as a argument pattern list or |
| %% as a non-negative integer that represents function clause/call arity. |
| %% |
| %% If an argument specification is given as an argument pattern, then every |
| %% pattern element corresponds to a function argument at the respective |
| %% position. '_' is a wildcard that matches any value. In fact you can specify |
| %% atom wildcard '_' at any level in the value structure. |
| %% (E.g.: {1, [blah, {'_', "bar", 2} | '_'], 3}). It is also possible to use a |
| %% {@link matcher()} created by {@link is/1} in-place of a value pattern. |
| %% |
| %% If an argument specification is given by an arity, then it is equivalent to |
| %% a pattern based argument specification that consists solely of wildcards, |
| %% and has the length of arity (e.g.: 3 is equivalent to ['_', '_', '_']). |
| |
| -type ret_spec() :: meck_ret_spec:ret_spec(). |
| %% Opaque data structure that specifies a value or a set of values to be returned |
| %% by a mock stub function defined by either {@link expect/3} and {@link expect/4}. |
| %% Values of `ret_spec()' are constructed by {@link seq/1}, {@link loop/1}, |
| %% {@link val/1}, {@link exec/1}, and {@link raise/2} functions. They are used |
| %% to specify return values in {@link expect/3} and {@link expect/4} functions, |
| %% and also as a parameter of the `stub_all' option of {@link new/2} function. |
| %% |
| %% Note that any Erlang term `X' is a valid `ret_spec()' equivalent to |
| %% `meck:val(X)'. |
| |
| -type func_clause_spec() :: {args_spec(), ret_spec()}. |
| %% It is used in {@link expect/3} and {@link expect/4} to define a function |
| %% clause of complex multi-clause expectations. |
| |
| %%%============================================================================ |
| %%% Interface exports |
| %%%============================================================================ |
| |
| %% @equiv new(Mod, []) |
| -spec new(Mods) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(). |
| new(Mod) when is_atom(Mod) -> new(Mod, []); |
| new(Mod) when is_list(Mod) -> lists:foreach(fun new/1, Mod), ok. |
| |
| %% @doc Creates new mocked module(s). |
| %% |
| %% This replaces the current version (if any) of the modules in `Mod' |
| %% with an empty module. |
| %% |
| %% Since this library is intended to use from test code, this |
| %% function links a process for each mock to the calling process. |
| %% |
| %% The valid options are: |
| %% <dl> |
| %% <dt>`passthrough'</dt> |
| %% <dd>Retains the original functions, if not mocked by meck. If used along |
| %% with `stub_all' then `stub_all' is ignored.</dd> |
| %% |
| %% <dt>`no_link'</dt> |
| %% <dd>Does not link the meck process to the caller process (needed for using |
| %% meck in rpc calls).</dd> |
| %% |
| %% <dt>`unstick'</dt> |
| %% <dd>Unstick the module to be mocked (e.g. needed for using meck with |
| %% kernel and stdlib modules).</dd> |
| %% |
| %% <dt>`no_passthrough_cover'</dt> |
| %% <dd>If cover is enabled on the module to be mocked then meck will continue |
| %% to capture coverage on passthrough calls. This option allows you to |
| %% disable that feature if it causes problems.</dd> |
| %% |
| %% <dt>`{spawn_opt, list()}'</dt> |
| %% <dd>Specify Erlang process spawn options. Typically used to specify |
| %% non-default, garbage collection options.</dd> |
| %% |
| %% <dt>`no_history'</dt> |
| %% <dd>Do not store history of meck calls.</dd> |
| %% |
| %% <dt>`non_strict'</dt> |
| %% <dd>A mock created with this option will allow setting expectations on |
| %% functions that does not exist in the mocked module. With this |
| %% option on it is even possible to mock non existing modules.</dd> |
| %% |
| %% <dt>`{stub_all, '{@link ret_spec()}`}'</dt> |
| %% <dd>Stubs all functions exported from the mocked module. The stubs will |
| %% return whatever defined by {@link ret_spec()} regardless of arguments |
| %% passed in. It is possible to specify this option as just `stub_all' |
| %% then stubs will return atom `ok'. If used along with `passthrough' |
| %% then `stub_all' is ignored. </dd> |
| %% |
| %% <dt>`merge_expects'</dt> |
| %% <dd>The expectations for the function/arity signature are merged with |
| %% existing ones instead of replacing all of them each time an |
| %% expectation is added. Expectations are added to the end of the |
| %% function clause list, meaning that pattern matching will be performed |
| %% in the order the expectations were added.</dd> |
| %% </dl> |
| %% |
| %% Possible exceptions: |
| %% <dl> |
| %% <dt>`error:{undefined_module, Mod}'</dt> |
| %% <dd>The module to be mocked does not exist. This error exists to prevent |
| %% mocking of misspelled module names. To bypass this and create a new |
| %% mocked module anyway, use the option `non_strict'.</dd> |
| %% <dt>`error:{module_is_sticky, Mod}'</dt> |
| %% <dd>The module to be mocked resides in a sticky directory. To unstick the |
| %% module and mock it anyway, use the option `unstick'.</dd> |
| %% <dt>`error:{abstract_code_not_found, Mod}'</dt> |
| %% <dd>The option `passthrough' was used but the original module has no |
| %% abstract code which can be called. Make sure the module is compiled |
| %% with the compiler option `debug_info'.</dd> |
| %% </dl> |
| |
| -spec new(Mods, Options) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Options :: [proplists:property()]. |
| new(Mod, Options) when is_atom(Mod), is_list(Options) -> |
| meck_proc:start(Mod, Options); |
| new(Mod, Options) when is_list(Mod) -> |
| lists:foreach(fun(M) -> new(M, Options) end, Mod), |
| ok. |
| |
| %% @doc Add expectation for a function `Func' to the mocked modules `Mod'. |
| %% |
| %% An expectation is either of the following: |
| %% <dl> |
| %% <dt>`function()'</dt><dd>a stub function that is executed whenever the |
| %% function `Func' is called. The arity of `function()' identifies for which |
| %% particular `Func' variant an expectation is created for (that is, a function |
| %% with arity 2 will generate an expectation for `Func/2').</dd> |
| %% <dt>`['{@link func_clause_spec()}`]'</dt><dd>a list of {@link |
| %% arg_spec()}/{@link ret_spec()} pairs. Whenever the function `Func' is called |
| %% the arguments are matched against the {@link arg_spec()} in the list. As |
| %% soon as the first match is found then a value defined by the corresponding |
| %% {@link ret_spec()} is returned.</dd> |
| %% </dl> |
| %% |
| %% It affects the validation status of the mocked module(s). If an |
| %% expectation is called with the wrong number of arguments or invalid |
| %% arguments the mock module(s) is invalidated. It is also invalidated if |
| %% an unexpected exception occurs. |
| -spec expect(Mods, Func, Expectation) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Expectation :: function() | [func_clause_spec()]. |
| expect(Mod, Func, Expectation) when is_list(Mod) -> |
| lists:foreach(fun(M) -> expect(M, Func, Expectation) end, Mod), |
| ok; |
| expect(_Mod, _Func, []) -> |
| erlang:error(empty_clause_list); |
| expect(Mod, Func, Expectation) when is_atom(Mod), is_atom(Func) -> |
| Expect = meck_expect:new(Func, Expectation), |
| check_expect_result(meck_proc:set_expect(Mod, Expect)). |
| |
| %% @doc Adds an expectation with the supplied arity and return value. |
| %% |
| %% This creates an expectation that has the only clause {`ArgsSpec', `RetSpec'}. |
| %% |
| %% @equiv expect(Mod, Func, [{ArgsSpec, RetSpec}]) |
| -spec expect(Mods, Func, ArgsSpec, RetSpec) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| ArgsSpec :: args_spec(), |
| RetSpec :: ret_spec(). |
| expect(Mod, Func, ArgsSpec, RetSpec) when is_list(Mod) -> |
| lists:foreach(fun(M) -> expect(M, Func, ArgsSpec, RetSpec) end, Mod), |
| ok; |
| expect(Mod, Func, ArgsSpec, RetSpec) when is_atom(Mod), is_atom(Func) -> |
| Expect = meck_expect:new(Func, ArgsSpec, RetSpec), |
| check_expect_result(meck_proc:set_expect(Mod, Expect)). |
| |
| %% @equiv expect(Mod, Func, Ari, seq(Sequence)) |
| %% @deprecated Please use {@link expect/3} or {@link expect/4} along with |
| %% {@link ret_spec()} generated by {@link seq/1}. |
| -spec sequence(Mods, Func, Ari, Sequence) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Ari :: byte(), |
| Sequence :: [any()]. |
| sequence(Mod, Func, Ari, Sequence) |
| when is_atom(Mod), is_atom(Func), is_integer(Ari), Ari >= 0 -> |
| Expect = meck_expect:new(Func, Ari, meck_ret_spec:seq(Sequence)), |
| check_expect_result(meck_proc:set_expect(Mod, Expect)); |
| sequence(Mod, Func, Ari, Sequence) when is_list(Mod) -> |
| lists:foreach(fun(M) -> sequence(M, Func, Ari, Sequence) end, Mod), |
| ok. |
| |
| %% @equiv expect(Mod, Func, Ari, loop(Loop)) |
| %% @deprecated Please use {@link expect/3} or {@link expect/4} along with |
| %% {@link ret_spec()} generated by {@link loop/1}. |
| -spec loop(Mods, Func, Ari, Loop) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Ari :: byte(), |
| Loop :: [any()]. |
| loop(Mod, Func, Ari, Loop) |
| when is_atom(Mod), is_atom(Func), is_integer(Ari), Ari >= 0 -> |
| Expect = meck_expect:new(Func, Ari, meck_ret_spec:loop(Loop)), |
| check_expect_result(meck_proc:set_expect(Mod, Expect)); |
| loop(Mod, Func, Ari, Loop) when is_list(Mod) -> |
| lists:foreach(fun(M) -> loop(M, Func, Ari, Loop) end, Mod), |
| ok. |
| |
| %% @doc Deletes an expectation. |
| %% |
| %% Deletes the expectation for the function `Func' with the matching |
| %% arity `Arity'. |
| %% `Force' is a flag to delete the function even if it is passthrough. |
| -spec delete(Mods, Func, Ari, Force) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Ari :: byte(), |
| Force :: boolean(). |
| delete(Mod, Func, Ari, Force) |
| when is_atom(Mod), is_atom(Func), Ari >= 0 -> |
| meck_proc:delete_expect(Mod, Func, Ari, Force); |
| delete(Mod, Func, Ari, Force) when is_list(Mod) -> |
| lists:foreach(fun(M) -> delete(M, Func, Ari, Force) end, Mod), |
| ok. |
| |
| %% @doc Deletes an expectation. |
| %% |
| %% Deletes the expectation for the function `Func' with the matching |
| %% arity `Arity'. |
| %% If the mock has passthrough enabled, this function restores the |
| %% expectation to the original function. See {@link delete/4}. |
| -spec delete(Mods, Func, Ari) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Ari :: byte(). |
| delete(Mod, Func, Ari) -> |
| delete(Mod, Func, Ari, false). |
| |
| %% @doc Returns the list of expectations. |
| %% |
| %% Returns the list of MFAs that were replaced by expectations |
| %% If `ExcludePassthrough' is on, only expectations that are not |
| %% direct passthroughs are returned |
| -spec expects(Mods, ExcludePassthrough) -> [{Mod, Func, Ari}] when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Ari :: byte(), |
| ExcludePassthrough :: boolean(). |
| expects(Mod, ExcludePassthrough) when is_atom(Mod) -> |
| meck_proc:list_expects(Mod, ExcludePassthrough); |
| expects(Mods, ExcludePassthrough) when is_list(Mods) -> |
| [Expect || Mod <- Mods, Expect <- expects(Mod, ExcludePassthrough)]. |
| |
| %% @doc Returns the list of expectations. |
| %% |
| %% Returns the list of MFAs that were replaced by expectations |
| -spec expects(Mods) -> [{Mod, Func, Ari}] when |
| Mods :: Mod | [Mod], |
| Mod :: atom(), |
| Func :: atom(), |
| Ari :: byte(). |
| expects(Mod) -> |
| expects(Mod, false). |
| |
| %% @doc Throws an expected exception inside an expect fun. |
| %% |
| %% This exception will get thrown without invalidating the mocked |
| %% module. That is, the code using the mocked module is expected to |
| %% handle this exception. |
| %% |
| %% <em>Note: this code should only be used inside an expect fun.</em> |
| -spec exception(Class, Reason) -> no_return() when |
| Class :: throw | error | exit, |
| Reason :: any(). |
| exception(Class, Reason) when Class == throw; Class == error; Class == exit -> |
| erlang:throw(meck_ret_spec:raise(Class, Reason)). |
| |
| %% @doc Calls the original function (if existing) inside an expectation fun. |
| %% |
| %% <em>Note: this code should only be used inside an expect fun.</em> |
| -spec passthrough(Args) -> Result when |
| Args :: [any()], |
| Result :: any(). |
| passthrough(Args) when is_list(Args) -> |
| {Mod, Func} = meck_code_gen:get_current_call(), |
| erlang:apply(meck_util:original_name(Mod), Func, Args). |
| |
| %% @doc Validate the state of the mock module(s). |
| %% |
| %% The function returns `true' if the mocked module(s) has been used |
| %% according to its expectations. It returns `false' if a call has |
| %% failed in some way. Reasons for failure are wrong number of |
| %% arguments or non-existing function (undef), wrong arguments |
| %% (function clause) or unexpected exceptions. |
| %% |
| %% Validation can detect: |
| %% |
| %% <ul> |
| %% <li>When a function was called with the wrong argument types |
| %% (`function_clause')</li> |
| %% <li>When an exception was thrown</li> |
| %% <li>When an exception was thrown and expected (via meck:exception/2), |
| %% which still results in `true' being returned</li> |
| %% </ul> |
| %% |
| %% Validation cannot detect: |
| %% |
| %% <ul> |
| %% <li>When you didn't call a function</li> |
| %% <li>When you called a function with the wrong number of arguments |
| %% (`undef')</li> |
| %% <li>When you called an undefined function (`undef')</li> |
| %% </ul> |
| %% |
| %% The reason Meck cannot detect these cases is because of how it is implemented. |
| %% Meck replaces the module with a mock and a process that maintains the mock. |
| %% Everything Meck get goes through that mock module. Meck does not insert |
| %% itself at the caller level (i.e. in your module or in your test case), so it |
| %% cannot know that you failed to call a module. |
| %% |
| %% Use the {@link history/1} or {@link history/2} function to analyze errors. |
| -spec validate(Mods) -> boolean() when |
| Mods :: Mod | [Mod], |
| Mod :: atom(). |
| validate(Mod) when is_atom(Mod) -> |
| meck_proc:validate(Mod); |
| validate(Mod) when is_list(Mod) -> |
| not lists:member(false, [validate(M) || M <- Mod]). |
| |
| %% @doc Return the call history of the mocked module for all processes. |
| %% |
| %% @equiv history(Mod, '_') |
| -spec history(Mod) -> history() when |
| Mod :: atom(). |
| history(Mod) when is_atom(Mod) -> meck_history:get_history('_', Mod). |
| |
| %% @doc Return the call history of the mocked module for the specified process. |
| %% |
| %% Returns a list of calls to the mocked module and their results for |
| %% the specified `Pid'. Results can be either normal Erlang terms or |
| %% exceptions that occurred. |
| %% |
| %% @see history/1 |
| %% @see called/3 |
| %% @see called/4 |
| %% @see num_calls/3 |
| %% @see num_calls/4 |
| -spec history(Mod, OptCallerPid) -> history() when |
| Mod :: atom(), |
| OptCallerPid :: '_' | pid(). |
| history(Mod, OptCallerPid) |
| when is_atom(Mod), is_pid(OptCallerPid) orelse OptCallerPid == '_' -> |
| meck_history:get_history(OptCallerPid, Mod). |
| |
| %% @doc Unloads all mocked modules from memory. |
| %% |
| %% The function returns the list of mocked modules that were unloaded |
| %% in the process. |
| -spec unload() -> Unloaded when |
| Unloaded :: [Mod], |
| Mod :: atom(). |
| unload() -> |
| fold_mocks(fun(Mod, Acc) -> |
| try |
| unload(Mod), |
| [Mod | Acc] |
| catch error:{not_mocked, Mod} -> |
| Acc |
| end |
| end, []). |
| |
| %% @doc Unload a mocked module or a list of mocked modules. |
| %% |
| %% This will purge and delete the module(s) from the Erlang virtual |
| %% machine. If the mocked module(s) replaced an existing module, this |
| %% module will still be in the Erlang load path and can be loaded |
| %% manually or when called. |
| -spec unload(Mods) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(). |
| unload(Mod) when is_atom(Mod) -> |
| meck_proc:stop(Mod), |
| wait_for_exit(Mod); |
| unload(Mods) when is_list(Mods) -> |
| lists:foreach(fun unload/1, Mods), ok. |
| |
| %% @doc Returns whether `Mod:Func' has been called with `Args'. |
| %% |
| %% @equiv called(Mod, Fun, Args, '_') |
| -spec called(Mod, OptFun, OptArgsSpec) -> boolean() when |
| Mod :: atom(), |
| OptFun :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(). |
| called(Mod, OptFun, OptArgsSpec) -> |
| meck_history:num_calls('_', Mod, OptFun, OptArgsSpec) > 0. |
| |
| %% @doc Returns whether `Pid' has called `Mod:Func' with `Args'. |
| %% |
| %% This will check the history for the module, `Mod', to determine |
| %% whether process `Pid' call the function, `Fun', with arguments, `Args'. If |
| %% so, this function returns true, otherwise false. |
| %% |
| %% Wildcards can be used, at any level in any term, by using the underscore |
| %% atom: ``'_' '' |
| %% |
| %% @see called/3 |
| -spec called(Mod, OptFun, OptArgsSpec, OptCallerPid) -> boolean() when |
| Mod :: atom(), |
| OptFun :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(), |
| OptCallerPid :: '_' | pid(). |
| called(Mod, OptFun, OptArgsSpec, OptPid) -> |
| meck_history:num_calls(OptPid, Mod, OptFun, OptArgsSpec) > 0. |
| |
| %% @doc Returns the number of times `Mod:Func' has been called with `Args'. |
| %% |
| %% @equiv num_calls(Mod, Fun, Args, '_') |
| -spec num_calls(Mod, OptFun, OptArgsSpec) -> non_neg_integer() when |
| Mod :: atom(), |
| OptFun :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(). |
| num_calls(Mod, OptFun, OptArgsSpec) -> |
| meck_history:num_calls('_', Mod, OptFun, OptArgsSpec). |
| |
| %% @doc Returns the number of times process `Pid' has called `Mod:Func' |
| %% with `Args'. |
| %% |
| %% This will check the history for the module, `Mod', to determine how |
| %% many times process `Pid' has called the function, `Fun', with |
| %% arguments, `Args' and returns the result. |
| %% |
| %% @see num_calls/3 |
| -spec num_calls(Mod, OptFun, OptArgsSpec, OptCallerPid) -> |
| non_neg_integer() when |
| Mod :: atom(), |
| OptFun :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(), |
| OptCallerPid :: '_' | pid(). |
| num_calls(Mod, OptFun, OptArgsSpec, OptPid) -> |
| meck_history:num_calls(OptPid, Mod, OptFun, OptArgsSpec). |
| |
| %% @doc Blocks until either function `Mod:Func' is called at least once with |
| %% arguments matching `OptArgsSpec', or `Timeout' has elapsed. In the latter |
| %% case the call fails with `error:timeout'. |
| %% |
| %% The number of calls is counted starting from the most resent call to |
| %% {@link reset/1} on the mock or from the mock creation, whichever occurred |
| %% latter. If a matching call has already occurred, then the function returns |
| %% `ok' immediately. |
| %% |
| %% @equiv wait(1, Mod, OptFunc, OptArgsSpec, '_', Timeout) |
| -spec wait(Mod, OptFunc, OptArgsSpec, Timeout) -> ok when |
| Mod :: atom(), |
| OptFunc :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(), |
| Timeout :: non_neg_integer(). |
| wait(Mod, OptFunc, OptArgsSpec, Timeout) -> |
| wait(1, Mod, OptFunc, OptArgsSpec, '_', Timeout). |
| |
| %% @doc Blocks until either function `Mod:Func' is called at least `Times' with |
| %% arguments matching `OptArgsSpec', or `Timeout' has elapsed. In the latter |
| %% case the call fails with `error:timeout'. |
| %% |
| %% The number of calls is counted starting from the most resent call to |
| %% {@link reset/1} on the mock or from the mock creation, whichever occurred |
| %% latter. If `Times' number of matching calls has already occurred, then the |
| %% function returns `ok' immediately. |
| %% |
| %% @equiv wait(Times, Mod, OptFunc, OptArgsSpec, '_', Timeout) |
| -spec wait(Times, Mod, OptFunc, OptArgsSpec, Timeout) -> ok when |
| Times :: pos_integer(), |
| Mod :: atom(), |
| OptFunc :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(), |
| Timeout :: non_neg_integer(). |
| wait(Times, Mod, OptFunc, OptArgsSpec, Timeout) -> |
| wait(Times, Mod, OptFunc, OptArgsSpec, '_', Timeout). |
| |
| %% @doc Blocks until either function `Mod:Func' is called at least `Times' with |
| %% arguments matching `OptArgsSpec' by process `OptCallerPid', or `Timeout' has |
| %% elapsed. In the latter case the call fails with `error:timeout'. |
| %% |
| %% The number of calls is counted starting from the most resent call to |
| %% {@link reset/1} on the mock or from the mock creation, whichever occurred |
| %% latter. If `Times' number of matching call has already occurred, then the |
| %% function returns `ok' immediately. |
| -spec wait(Times, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) -> ok when |
| Times :: pos_integer(), |
| Mod :: atom(), |
| OptFunc :: '_' | atom(), |
| OptArgsSpec :: '_' | args_spec(), |
| OptCallerPid :: '_' | pid(), |
| Timeout :: non_neg_integer(). |
| wait(0, _Mod, _OptFunc, _OptArgsSpec, _OptCallerPid, _Timeout) -> |
| ok; |
| wait(Times, Mod, OptFunc, OptArgsSpec, OptCallerPid, Timeout) |
| when is_integer(Times) andalso Times > 0 andalso |
| is_integer(Timeout) andalso Timeout >= 0 -> |
| ArgsMatcher = meck_args_matcher:new(OptArgsSpec), |
| meck_proc:wait(Mod, Times, OptFunc, ArgsMatcher, OptCallerPid, Timeout). |
| |
| %% @doc Erases the call history for a mocked module or a list of mocked modules. |
| %% |
| %% This function will erase all calls made heretofore from the history of the |
| %% specified modules. It is intended to prevent cluttering of test results with |
| %% calls to mocked modules made during the test setup phase. |
| -spec reset(Mods) -> ok when |
| Mods :: Mod | [Mod], |
| Mod :: atom(). |
| reset(Mod) when is_atom(Mod) -> |
| meck_proc:reset(Mod); |
| reset(Mods) when is_list(Mods) -> |
| lists:foreach(fun(Mod) -> reset(Mod) end, Mods). |
| |
| %% @doc Converts a list of terms into {@link ret_spec()} defining a loop of |
| %% values. It is intended to be in construction of clause specs for the |
| %% {@link expect/3} function. |
| %% |
| %% Calls to an expect, created with {@link ret_spec()} returned by this function, |
| %% will return one element at a time from the `Loop' list and will restart at |
| %% the first element when the end is reached. |
| -spec loop(Loop) -> ret_spec() when |
| Loop :: [ret_spec()]. |
| loop(Loop) -> meck_ret_spec:loop(Loop). |
| |
| %% @doc Converts a list of terms into {@link ret_spec()} defining a sequence of |
| %% values. It is intended to be in construction of clause specs for the |
| %% {@link expect/3} function. |
| %% |
| %% Calls to an expect, created with {@link ret_spec} returned by this function, |
| %% will exhaust the `Sequence' list of return values in order until the last |
| %% value is reached. That value is then returned for all subsequent calls. |
| -spec seq(Sequence) -> ret_spec() when |
| Sequence :: [ret_spec()]. |
| seq(Sequence) -> meck_ret_spec:seq(Sequence). |
| |
| %% @doc Converts a term into {@link ret_spec()} defining an individual value. |
| %% It is intended to be in construction of clause specs for the |
| %% {@link expect/3} function. |
| -spec val(Value) -> ret_spec() when |
| Value :: any(). |
| val(Value) -> meck_ret_spec:val(Value). |
| |
| %% @doc Creates a {@link ret_spec()} that defines an exception. |
| %% |
| %% Calls to an expect, created with {@link ret_spec()} returned by this function, |
| %% will raise the specified exception. |
| -spec raise(Class, Reason) -> ret_spec() when |
| Class :: throw | error | exit, |
| Reason :: term. |
| raise(Class, Reason) -> meck_ret_spec:raise(Class, Reason). |
| |
| %% @doc Creates a {@link ret_spec()} that makes the original module function be |
| %% called. |
| %% |
| %% Calls to an expect, created with {@link ret_spec()} returned by this function, |
| %% will be forwarded to the original function. |
| -spec passthrough() -> ret_spec(). |
| passthrough() -> meck_ret_spec:passthrough(). |
| |
| %% @doc Creates a {@link ret_spec()} from a function. Calls to an expect, |
| %% created with {@link ret_spec()} returned by this function, will be forwarded |
| %% to the specified function. |
| -spec exec(fun()) -> ret_spec(). |
| exec(Fun) -> meck_ret_spec:exec(Fun). |
| |
| %% @doc creates a {@link matcher/0} instance from either `Predicate' or |
| %% `HamcrestMatcher'. |
| %% <ul> |
| %% <li>`Predicate' - is a single parameter function. If it returns `true' then |
| %% the argument passed to it is considered as meeting the matcher criteria, |
| %% otherwise as not.</li> |
| %% <li>`HamcrestMatcher' - is a matcher created by |
| %% <a href="https://github.com/hyperthunk/hamcrest-erlang">Hamcrest-Erlang</a> |
| %% library</li> |
| %% </ul> |
| -spec is(MatcherImpl) -> matcher() when |
| MatcherImpl :: Predicate | HamcrestMatcher, |
| Predicate :: fun((any()) -> any()), |
| HamcrestMatcher :: meck_matcher:hamcrest_matchspec(). |
| is(MatcherImpl) -> |
| meck_matcher:new(MatcherImpl). |
| |
| %% @doc Returns the value of an argument as it was passed to a particular |
| %% function call made by a particular process. It fails with `not_found' error |
| %% if a function call of interest has never been made. |
| %% |
| %% It retrieves the value of argument at `ArgNum' position as it was passed |
| %% to function call `Mod:Func' with arguments that match `OptArgsSpec' made by |
| %% process `CallerPid' that occurred `Occur''th according to the call history. |
| %% |
| %% Atoms `first' and `last' can be used in place of the occurrence number to |
| %% retrieve the argument value passed when the function was called the first |
| %% or the last time respectively. |
| %% |
| %% If an occurrence of a function call irrespective of the calling process needs |
| %% to be captured then `_' might be passed as `OptCallerPid', but it is better |
| %% to use {@link capture/5} instead. |
| -spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> ArgValue when |
| Occur :: first | last | pos_integer(), |
| Mod :: atom(), |
| Func :: atom(), |
| OptArgsSpec :: '_' | args_spec(), |
| ArgNum :: pos_integer(), |
| OptCallerPid :: '_' | pid(), |
| ArgValue :: any(). |
| capture(Occur, Mod, Func, OptArgsSpec, ArgNum, OptCallerPid) -> |
| meck_history:capture(Occur, OptCallerPid, Mod, Func, OptArgsSpec, ArgNum). |
| |
| %% @doc Returns the value of an argument as it was passed to a particular |
| %% function call, It fails with `not_found' error if a function call of |
| %% interest has never been made. |
| %% |
| %% It retrieves the value of argument at `ArgNum' position as it was passed |
| %% to function call `Mod:Func' with arguments that match `OptArgsSpec' that |
| %% occurred `Occur''th according to the call history. |
| %% |
| %% Atoms `first' and `last' can be used in place of the occurrence number to |
| %% retrieve the argument value passed when the function was called the first |
| %% or the last time respectively. |
| %% |
| %% @equiv capture(Occur, Mod, Func, OptArgsSpec, ArgNum, '_') |
| -spec capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> ArgValue when |
| Occur :: first | last | pos_integer(), |
| Mod::atom(), |
| Func::atom(), |
| OptArgsSpec :: args_spec(), |
| ArgNum :: pos_integer(), |
| ArgValue :: any(). |
| capture(Occur, Mod, Func, OptArgsSpec, ArgNum) -> |
| meck_history:capture(Occur, '_', Mod, Func, OptArgsSpec, ArgNum). |
| |
| %% @doc Returns the currently mocked modules. |
| -spec mocked() -> list(atom()). |
| mocked() -> |
| fold_mocks(fun(M, Acc) -> [M | Acc] end, []). |
| |
| %%%============================================================================ |
| %%% Internal functions |
| %%%============================================================================ |
| |
| -spec wait_for_exit(Mod::atom()) -> ok. |
| wait_for_exit(Mod) -> |
| MonitorRef = erlang:monitor(process, meck_util:proc_name(Mod)), |
| receive {'DOWN', MonitorRef, _Type, _Object, _Info} -> ok end. |
| |
| -spec fold_mocks(Fun, AccIn) -> AccOut when |
| Fun :: fun((Elem :: module(), AccIn) -> AccOut), |
| AccIn :: term(), |
| AccOut :: term(). |
| fold_mocks(Fun, Acc0) when is_function(Fun, 2) -> |
| lists:foldl(fun(Mod, Acc) -> |
| ModName = atom_to_list(Mod), |
| case lists:split(max(length(ModName) - 5, 0), ModName) of |
| {Name, "_meck"} -> Fun(erlang:list_to_existing_atom(Name), Acc); |
| _Else -> Acc |
| end |
| end, Acc0, erlang:registered()). |
| |
| -spec check_expect_result(ok | {error, Reason::any()}) -> ok. |
| check_expect_result(ok) -> ok; |
| check_expect_result({error, Reason}) -> erlang:error(Reason). |