Skip to content

Add meck:get_state/0, meck:reset_state/0 #125

Closed

Description

@rlipscombe

meck:new and meck:unload are quite slow. I attempted to use fixture setup and cleanup in eunit to only call them once for the fixture:

{setup, fun fixture_setup/0, fun fixture_cleanup/1,
  {foreach, fun case_setup/0, fun case_cleanup/1,
    Tests}}.

But then I have difficulty deleting expectations in case_cleanup: if I implement meck:delete/0, then it also removes expectations that were set up in fixture_setup and case_setup.

In particular passthrough and stub_all expectations.

So, I figure it'd be nice to be able to return an opaque term representing the current expectations at the end of case_setup. EUnit conveniently passes this to case_cleanup, where it could be restored with meck:reset_state/0.

This would save the slow meck:new and meck:unload on each case, but would guarantee that the state of the mocks was restored for the next test.

Activity

  1. eproxus commented on Jun 23, 2014

    @eproxus
    Owner

    With state of the mocks, do you only mean the validation state (meck:validate/1)?

  2. rlipscombe commented on Jun 23, 2014

    @rlipscombe
    ContributorAuthor

    No, I mean the entire state. I'd like (if possible) to pickle and unpickle the entire state of all extant mocks -- stubs, mocks and fakes. Not necessarily expectations.

    Let me take a step back here. The core problem is that meck:new is slow, so I'd like to avoid calling it for every case_setup.

    However, the only way for case_cleanup to erase any mocks set up in the individual tests is by using meck:unload().

    There is meck:delete, but that requires knowing which mocks to delete. So I implemented a meck_extras:delete_all. This required some deep poking around with sys:get_state, which is evil, so I can submit a PR for it, if that sounds like something that should be in core meck (where it won't need to be evil).

    But that removes any [passthrough] or [stub_all] expectations, which can only be set in meck:new. And we're trying to avoid calling meck:new.

    I guess, instead of saving/restoring state, meck:delete/0, meck:reset/0, meck:passthrough/1 and meck:stub_all/1 would be sufficient.

    Then I could have:

    fixture_setup() ->
        meck:new(Mods).
    fixture_cleanup() ->
        meck:unload().
    case_setup() ->
        meck:stub_all(ModsToStub),
        meck:passthrough(ModsToPassThrough),
        meck:expect(OtherStuff).
    case_cleanup() ->
        meck:reset(),
        meck:delete().
  3. rlipscombe commented on Jun 23, 2014

    @rlipscombe
    ContributorAuthor

    Oh, and the problem I found when attempting to write meck:stub_all(Mods) and meck:passthrough(Mods) is that, once you've called meck:new(Mod) there doesn't appear to be a way to get the exported functions any more.

  4. eproxus commented on Jul 1, 2014

    @eproxus
    Owner

    Ok, I see what you mean, I think.

    When creating a mock, a new module is compiled (and the state is saved in the Meck process). When adding functions, passthroughs etc. the whole module is recompiled (since there is no way to add functions to an existing module in Erlang). This leads to that every new and expect call recompiles the module (which takes some time).

    I suspect what you proposed would be equally slow to calling new, because everything will result in one or several recompilations anyway (new does some other things, like taking care of coverage etc., but that shouldn't be the major time consumption).

  5. eproxus commented on Nov 21, 2016

    @eproxus
    Owner

    Closing and documenting in the wiki instead: https://github.com/eproxus/meck/wiki#feature-ideas

    Please comment here if you have information or requests to add, and/or want to discuss a PR or possible implementation proposal.

  6. rlipscombe commented on Nov 21, 2016

    @rlipscombe
    ContributorAuthor

    I have an alternative, related, suggestion:

    We have a lot of fixture setup of the form:

    meck:new(foo),
    meck:expect(foo, bar, fun(A, B) -> Whatever end),
    meck:expect(foo, baz, fun(C) -> Whatever end),
    meck:expect(foo, quux, fun(Q) -> Whatever end),
    meck:expect(foo, wurdle, fun(X, Y) -> Whatever end),
    

    This requires recompiling the module on each meck:expect call. Would it be possible to add a way to add multiple expectations in one go, and only compiling once? Or maybe there is, and I missed it? Should I raise another issue for this?

  7. eproxus commented on Nov 21, 2016

    @eproxus
    Owner

    Yeah, this is something I thought about as well, to have mock templates that compile everything in one go. Good point.

    It's not possible at the moment, only different clauses for the same function at the same time. It's something I want to do for 2.0-something, to make the expects API more coherent and simpler (basically a tree structure of mod/func/clause, which would also make templating easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions