klajo / mockgyver

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

How to define a sequence of different replies from a mocked functions? #6

Open drapostolos opened 10 years ago

drapostolos commented 10 years ago

I'm mocking a function something like this:

 ?WHEN(m:f(a, b) -> ok),

The function I'm testing calls the mocked function multiple times. With the mock above, the return value is always 'ok'. Is there a way to define a sequence of different return values instead? Something like this:

 ?WHEN(m:f(a, b) -> ok;
       m:f(a, b) -> nok;
       m:f(a, b) -> ok),

Where the first call to m:f(a, b) returns 'ok', second call returns 'nok' and third call returns 'ok'.

klajo commented 10 years ago

Hi,

What you wrote in the example would be the same as writing this erlang function in m.erl:

f(a, b) -> ok;
f(a, b) -> nok;
f(a, b) -> ok.

I.e: dead code.

What you can do is to redefine the reply of your function in runtime, i.e. something like this:

?WHEN(m:f(a, b) -> ok),
... run test that would expect f/2 to return ok ...
?WHEN(m:f(a, b) -> nok),
... run test that would expect f/2 to return nok ...
?WHEN(...),

Other than that there's no way for a mock in mockgyver to have a "state", except for using something like ets to store and update a state between runs (implemented in the test case itself).

I've thought about a use for being able to return different values on subsequent calls but so far the above pattern has been enough. Do you have a good use case for stateful mocks? If so, it would be interesting to see.

Cheers, Klas

drapostolos commented 10 years ago

I'm testing a function foo/1, which internally calls ct_netconfc:open/1 and ct_netconfc:close_session/1 twice.

Something like :

foo(Foo) ->
    %% in a separate process
        ct_netconfc:open/1
        %% do things...
        ct_netconfc:close_session/1 

    %% in current process
        ct_netconfc:open/1
        %% do things...
        ct_netconfc:close_session/1 

I want to test the behavior of foo/1 when the second call to ct_netconfc:close_session/1 returns {error, error_reason()}, but the first ct_netconfc session closses with ok. It then would be handy to use stateful mocks, where first call to ct_netconfc:closesession/1 is mocked to return ok and the second call returns `{error, }`.

klajo commented 10 years ago

I see. Is there a defined order between those two processes (the separate and current) or can those calls happen in any order? If any order: then it would be hard to solve with a mock.

Right now there's no support for this in mockgyver, except for writing the code for it yourself in the test case (eg. letting the mock update some ets table or something like that).

I'm flagging this as a feature request, but I will unfortunately not have the time to implement it in the near future.

/Klas

drapostolos commented 10 years ago

Yes, the order is defined, where the netconf session in the "other" process happens before the netconf session in current process.

I found a workaround though, to mock away the spawning of the "other" process so it never happens, as i'm not interested in that.

/Alex

klajo commented 10 years ago

Ok. There may be a risk of mocking the spawn, depending how it's done. If it's a "system" call (eg. proc_lib:spawn_link, gen_tcp:send, io:format, ...) that's commonly used by other modules/processes in the system - especially ones within Erlang/OTP itself - you may risk affecting the behaviour of the entire system.

I've written about this in the README: https://github.com/klajo/mockgyver#caveats