Imagine you have a process which is responsible for consuming some sort of queue and spawning a separate process for each given job.
Each of the job processors later called workers communicates with some infrastructure layers for eg. external API, database, etc. We are injecting infrastructure to workers via configuration:
defp current_impl do
Application.get_env(:scanner, :ensure_file, __MODULE__.AwsImpl)
end
Each injected implementation must implement complementary behaviour for eg.
defmodule Scanner.Scans.EnsureFile.Impl do
@callback call(String.t, String.t) :: {:ok, any()} | {:error, any()}
end
In this setup, we could mock the whole behaviour using Mox
:
# test_suport.ex or other file with your test configuration
Mox.defmock(Scanner.EnsureFileMock, for: Scanner.Scans.EnsureFile.Impl)
Application.put_env(
:sanner,
:ensure_file,
Scanner.EnsureFileMock
)
# actual test
expect(Scanner.EnsureFileMock, :call, fn ^bucket, ^path ->
{:ok, :response}
end
When we run this test with the given context we would gen an error:
(Mox.UnexpectedCallError) no expectation defined for Scanner.EnsureFileMock.call/2
The reason is that Mox by default works in private
mode which is applying our mocked behaviour only for the current process - in our scenario our tested module spawns workers which are the real consumers of our mock and the implementation is only mocked for our test process. To allow our child process to consume the mock we need to set Mox in our test the global mode:
setup :set_mox_global
Or we could use:
setup :set_mox_from_context
Which will set the private
mode only when a test has async: true
otherwise it would use global mode.