Test interaction between modules?

Hello everyone,

TLDR; I really just want to test that my module called a function on another module, but so that it still works in parallel tests so preferably doesn’t use stubbing/mocking (or at least not as I know it)

More concrete example:

I have a module that some times needs to print something with IO.puts - it has gotten a bit much and not really pure so I thought that I might want to extract it into a separate module to separate concerns and help my testing be without capture_io. And then sort of do “dependency injection” of the module. So something like:

defmodule MyModule do
  def main_function(parameter, printer \\ DefaultPrinter), do: ...

And in the tests I would then use a different module that doesn’t print for real:

MyModule.main_function(params, MyFakeModule)

But, I still want to assert that a certain method was called (maybe with certain parameters) which is important to know as it’s an important property of my system that this method is called (printing out a warning for instance).

Of course, you might feel my OOP roots - what I’d want to do is stubbing/mocking to assert that something has been called. But stubbing/mocking is not really elixir-ish as it’s global state and that would also prevent running the tests in parallel.

So, what do I do? Right now I’m thinking about just still using capture_io when I care about if it prints or not with the default module and just using a fake “do nothing module” when I don’t care in the tests.

Any other ideas? All input appreciated :slight_smile:

The obvious way to do such thing is to use tracing. With tracing you can set up your process to receive a message whenever a function is called in a specified process - this sounds like a perfect solution for your question. I believe that’s now the bulk of meck works.
That said setting up the correct traces is complicated and might take several tries to get right.

A much simpler solution (that works only if you don’t spawn separate processes) is for the “fake” module functions to do something simple like:

def foo(a, b, c), do: send(self(), {:foo, a, b, c})

And you can later, in the test process assert_receive on the message. That’s a very common approach that is used, for example, for testing ecto adapters.

1 Like

That sounds great, thanks for the input Michal I’ll try the send - great idea. Might even dig into the tracing, but just because it’s been an interest of myself either way :slight_smile: