Feedback wanted - simple mocking library

I just published, mostly to solicit feedback, a minimalistic mocking library.

Docs, Hex package, Sample test code and a Larger test suite (it is basically helping me TDD a Raft library) are available.

At the moment, the library is very much scratching my own itch, and I’m reasonably happy with it so far. I wonder if others would be interested, what changes would need to be made, etcetera, before I put in the work to properly publish it. Any feedback welcome :slight_smile:

Teaser-so-you-don’t-need-t-click-through:

test "default reply works" do
    {:ok, mock_dependency = {_mod, _pid}} = Mock.with_expectations do
      expect_call some_call(_some_pid, "you", "me")
    end
    {:ok, mut} = ModuleUnderTest.start_link(mock_dependency)

    assert :ok == ModuleUnderTest.do_something_with_dependency(mut)

    Mock.verify(mock_dependency)
  end
1 Like

Hi @cdegroot,

I just stumbled upon this post while looking for activity in the community around mocks and mock libraries. I just put my hands off the first version of elixir_mock - a new “mock-making” library after getting frustrated with the lack of options currently available to create inspectable mocks.

While looking to create a post seeking feedback from the community about elixir_mock, I stumbled into this post. I have looked at your library and I am happy with the following:

  • The library creates mocks, it doesn’t mock. I have not dug deep enough into the code but it appears that you are creating a new module each time a mock is defined. If that is the case, you have a thumbs up!
  • The library allows you to set mock expectations at mock definition time. The makes the final Mock.verify(mock) statement concise and simple.

However, because you define the expected calls at mock definition time, the ability to read the tests from top to bottom is lost. What I mean is that in a traditional unit test setup, you have the following steps: Setup, Act and then Assert. The last part (the assertion) is very important because it documents for the person reading the test what effects/results the action should have. The way you have designed your API is that the final statement in the test is Mock.verify(mock). This means that in order for me to know what is being verified, I need to skip back a few lines to see what I am verifying. (I understand that this pattern is used in other languages’ testing libraries. I am just expressing my opinion on the pattern.)

Questions

  • I have failed to understand why the mock you generate looks like {mod, pid}. It doesn’t look to me like the user of the library needs to care about that implementation detail. I would suggest you find a way to wrap GenServer stuff into a clean module when a mock is created. Please enlighten me if there is something obvious I am missing.

Request
I’ll really appreciate your feedback If you can find some time to look at elixir_mock.

1 Like

That, of course, is just one particular style. Others are possible and in use. Frankly, if your tests are small, you don’t need to read up and down. If your tests are large, you need to make them smaller no matter what order or style you use.

That’s because most of these mocks mock two things at once - the module’s API, and a (GenServer) process collaborator. In production code that is mockable, I often found that I’d both have a module (the actual implementation I’m talking against) and a pid (the GenServer process that’s essentially the instance of the implementation). For example, when talking to MySQL:

{:ok, pid} = Mysqlex.Connection.start_link(username: "test", database: "test", password: "test", hostname: "10.0.3.82")
Mysqlex.Connection.query(pid, "CREATE TABLE posts (id serial, title text)")

In this case, the Mysqlex.Connection is the (mockable) module, and pid the instance I want to talk to. Say that I use a sort of dependency injection style, I’d pass {Mysqlex.Connection, pid} to a module that has the database as a dependency.

In fact, I got to the pattern in Simpler because mocking a naked module is trivial - if I test code that just requires some behaviour, I can simply do:

test "some test" do
  defmodule SomeMock do
    def my_method(args), do: "42"
  end
  assert 42 == MyCodeUnderTest.test_this(SomeMock, args)
end

which isn’t much harder than using ex_mock to do essentially the same if my quick stroll through the readme has me on the right path. Note that - again, if I understood ex_mock correctly - the above style will only mock the exact interface that MyCodeUnderTest needs, and as such serves as specification/documentation about this particular collaboration, more than a mock that just has a generated stub for every module. In essence, when you generate a dummy module with a list of public methods from some other module, you’re mocking that module’s implementation instead of the required interface your code under test needs from this particular collaborator. That’s usually not the best way to mock.

So Simpler, in essence, tries to tackle the harder bit of mocking something that’s close to an object in Elixir - a combination of behaviour (the module) and state (the pid). A lot of such interactions can be seen in my Raft code (I’ve made it a point to develop Simpler alongside “actual code” I’m writing, so it fulfills just the needs of my test code), like here - the persistence part of Raft is mocked and the mock, concisely, specifies how the interaction with the database should ensue. Note that given the widespread occurrence of the {module, pid} pattern once you start making the module bit of collaborator mockable, I very often pass it on as a single argument and only unpack the tuple when I need to make an actual call in production code, like for example here - it’s almost ugly enough to warrant some macro magic but I haven’t crossed my personal pain threshold ;).

Hope that background explanation helps. And +1 on helping to build proper testing patterns - meck and relatives that hack global module names are a scourge that should be wiped from the earth! async: true is our best path to sane testing.