How to mock objects in Erlang using Meck?

5.8k views Asked by At

Okay, I'm using Meck and I'm lost. My first language (that I've been writing for about 7 months) is Ruby, so I can't seem to wrap my brain around Meck mocking yet. I do get Ruby mocking though. Hoping someone can help me. Also, I've only been writing Erlang for a week.

Updated Code (but mocking still isn't working)...

I have a Erlang console_io prompter module that looks like this:

    -module(prompter).
    -export([prompt/1, guess/0]).

    prompt(Message) ->
      console_io:gets(Message).

    gets() ->
      {_, [Input]} = io:fread("Enter:  ", "~s"),
      Input.

    guess() ->
      Guess_Input = gets(),
      Guess_List = convert_guess_to_list(Guess_Input).

    convert_guess_to_list(Guess_Input) ->
      re:split(Guess_Input, "", [{return, list}, trim]).

My test now looks like this:

    -module(prompter_test).
    -include_lib("eunit/include/eunit.hrl").

    guess_1_test() ->
      meck:new(prompter),
      meck:expect(prompter, gets, fun() -> "aaaa" end),
      ?assertEqual(prompter:guess(), ["a","a","a","a"]),
      ?assert(meck:validate(prompter)),
      meck:unload(prompter).

The error I'm getting is this:

    Eshell V5.9.3.1  (abort with ^G)
    1> prompter_test: guess_1_test (module 'prompter_test')...*failed*
    in function prompter:guess/0
      called as guess()
    in call from prompter_test:guess_1_test/0 (test/prompter_test.erl, line 10)
    in call from prompter_test:guess_1_test/0
    **error:undef

I want to mock (stub?) the gets function in my test so that gets will return "aaaa" and then when I assert on get_guess() it should equal ["a", "a", "a", "a"].

How do I do this?

2

There are 2 answers

1
legoscia On BEST ANSWER

There are two problems:

  • The prompter module has two exported functions, but you only mock one of them (gets) with meck:expect. By default, Meck creates a new module that only contains the functions that you explicitly mock. You can change that by using the passthrough option:

    meck:new(prompter, [passthrough]),
    
  • When you mock the gets function, all module-prefixed calls (i.e. prompter:gets()) are intercepted, but Meck has no way (yet?) of intercepting internal calls (e.g. the gets() call in the guess function), so you would still get the unmocked version of the function. There is no completely satisfactory way to avoid this. You could change the call in guess to prompter:gets(), or you could move gets into a separate module and mock that.

3
kjw0188 On

The first line says to create a new mocked module, my_library_module:

    meck:new(my_library_module),

Next, we mock the function fib in my_library_module to return 21 when 8 is passed in:

    meck:expect(my_library_module, fib, fun(8) -> 21 end),

We have some eunit assertions to test our mocked function. The code_under_test:run call is what you want to replace with the the function using your mocked module, and the 21 is the result you are expecting from the function call:

    ?assertEqual(21, code_under_test:run(fib, 8)), % Uses my_library_module
    ?assert(meck:validate(my_library_module)),

Then we unload the mocked module:

    meck:unload(my_library_module).

If you wanted to write the same test for your module, you could write:

my_test() ->
    meck:new(console_io),
    meck:expect(console_io, gets, fun() -> "aaaa" end),
    ?assertEqual(["a", "a", "a", "a"], console_io:get_guess()), % Uses console_io
    ?assert(meck:validate(console_io)),
    meck:unload(console_io).