Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expect different return values at different times #147

Closed
doubleyou opened this issue Jul 2, 2015 · 2 comments
Closed

Expect different return values at different times #147

doubleyou opened this issue Jul 2, 2015 · 2 comments
Assignees
Milestone

Comments

@doubleyou
Copy link

Hi,

Consider the following example. We want some mocked function to return different values in different cases. Normally, this is being done either using something like meck:seq/1 or by calling meck:expect/4 in-place.

The sequencing approach can be a problem if there's no determined mount of calls to be made. For example, when we have some periodic calls, or when we have a complex call tree that calls our mocked function not once or twice. We may not even care about the amount of function calls made, just the fact that its return value was always the defined one.

Calling meck:expect/4 in place is much better from the semantics perspective but it's painfully slow, as we recompile the module every time. As a result, simple tests may start taking 5-10 seconds instead of sub-seconds.

Is it technically possible to use something like ETS tables for storing meck:expect table, or there were some technical limitations that made you require to recompile the module? I ended up having that ETS-based approach for some cases and now wonder if meck:expect/4 could be changed to work this way, which is much faster, or another function like meck:quick_expect/4 can be added as a wrapper, because this seems like a fairly common pattern.

Thanks!

@eproxus
Copy link
Owner

eproxus commented Jul 3, 2015

To recap, the following is possible right now with Meck:

  • Static return values
  • Static sequences (seq/sequence and loop)
  • Static pattern matches (expect plus a fun with different function headers`)

A more robust example of the last case would be:

a_test() ->
    meck:expect(mod, f, fun mock/3).

mock(1,    Arg2, Arg3) -> 1;
mock(Arg1,    2, Arg3) -> 2;
mock(Arg1, Arg2, Arg3) -> etc.

A more complex method is of course to call some other code or process to do something more advanced. You could check which time it is, how many calls have been made, which process is calling etc. This is essentially a mock with a state and some advanced logic.

It can currently be implemented like so:

a_test() ->
    Pid = start(InitState),
    meck:expect(mod, f, fun(A1, A2, A3) -> call(Pid, [A1, A2, A3]) end).

start(State) -> spawn_link(fun() -> advanced_mock(State) end).

call(Pid, Args) ->
    Pid ! {call, pid(), Args},
    receive {reply, Reply} -> Reply end.

advanced_mock(State) ->
    receive
        {call, From, Args} ->
            From ! {reply, do_something(Args, From, State)},
            advanced_mock(State)
    end.

I agree with what you say, and I think there is a use case for making the last option somewhat easier with Meck, if possible. How, I'm not so sure.

One idea I've toyed with is a meck:put/2 and meck:get/1 that could be called inside a mock to keep state, but it is not very expressive and still forces you to write a lot of boiler plate code.

Just return data from an ETS table I think is also too simplistic. It's just seq or loop on steroids.

Ideally, Meck should provide some structure for keeping and modifying state along with an expressive way to define this. Alongside, it should also make it possible to affect that state without triggering complete recompiles.

@eproxus eproxus added this to the 1.0 milestone Nov 21, 2016
@eproxus eproxus self-assigned this Nov 21, 2016
@eproxus eproxus modified the milestones: 2.0+, 1.0 Mar 27, 2017
@eproxus
Copy link
Owner

eproxus commented Feb 17, 2021

With multiple function clause specs I think this is currently possible to achieve somewhat satisfactory. Closing this for now. Feel free to open a new issue or discussion about any improvements.

@eproxus eproxus closed this as completed Feb 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants