I am trying to create a mock for one of my behaviours. To this end I am using Hammox (a variation of Mox which extends the api).
I have set my application so it gets which module to use from the configuration files, in this case config/test.exs
:
config/test.exs
config :my_app, storage: StorageMock
And this is the test file:
test/my_app/application_test.exs
defmodule MyApp.ApplicationTest do
@moduledoc false
use ExUnit.Case, async: false
use MyApp.ConnCase
import Hammox
test "client can handle an error response", %{conn: conn} do
Hammox.defmock(StorageMock, for: MyApp.Storage)
expect(StorageMock, :get, fn args ->
assert args == "Chicago"
# here we decide what the mock returns
{:ok, %{body: "Some html with weather data"}}
end)
get(conn, "~p/api/users/validate")
end
end
I am trying to test the controller's endpoint,and I am basically mixing the docs of Hammox (https://github.com/msz/hammox) with what i can grasp from a Testing controllers guide (https://hexdocs.pm/phoenix/testing_controllers.html#content).
Unfortunately, I think my setup is incorrect, as I get this error:
Compiling 1 file (.ex)
** (Mix) Could not start application my_app: exited in: MyApp.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (ArgumentError) The module StorageMock was given as a child to a supervisor but it does not exist
(elixir 1.16.0) lib/supervisor.ex:797: Supervisor.init_child/1
(elixir 1.16.0) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
(elixir 1.16.0) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
(elixir 1.16.0) lib/supervisor.ex:783: Supervisor.init/2
(elixir 1.16.0) lib/supervisor.ex:707: Supervisor.start_link/2
(kernel 9.2) application_master.erl:293: :application_master.start_it_old/4
What am I missing here?
I assume there is some configuration somewhere to tell the VM that StorageMock
is something from the library, but I can't find it.
Turns out my issue was that I was referencing StorageMock
at application startup, namely:
defmodule MyApp.Application do
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children =
[
MyAppWeb.Telemetry,
{Phoenix.PubSub, name: MyApp.PubSub},
{StorageMock, []},
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
By removing StorageMock
from the list of children (when running tests) the error was fixed.
For those of you curious, you can check in which env the application is running, by using Mix.env()
. Thus, you can do something like:
def start(_type, _args) do
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children(Mix.env(), opts)
end
defp children(:test) do
[
MyAppWeb.Telemetry,
{Phoenix.PubSub, name: MyApp.PubSub},
MyAppWeb.Endpoint
]
end
defp children(_other_envs) do
[
MyAppWeb.Telemetry,
{Phoenix.PubSub, name: MyApp.PubSub},
{RealStorage, ["localhost:5221"]},
MyAppWeb.Endpoint
]
end
This is the solution I ended up going with, as it means you can make use of the config/test.exs
to define what modules you want to use. You can even change modules at runtime by using Application.put_env/3
if you are inclined to do that, which can also come in handy when doing some specific types of tests.
Another viable approach can seen in this comment from the community:
test_helper.exs
is also loaded after application startup. What works is putting thedefmock
in a compiled file (not a script), e.g. intest/support/mocks.ex
or something like that. See the documentation here: Mox — Mox v1.1.0
Which states that there is a compile time approach to also solving this. Original post: https://elixirforum.com/t/how-to-test-a-phoenix-controller-with-hammox/64191/5?u=fl4m3ph03n1x