如何用Hammox测试Phoenix控制器?启动遇模块不存在错误
我正尝试为自定义Behaviour创建Mock,使用的是Hammox(Mox的扩展版本)。已在config/test.exs中配置应用使用StorageMock:
config :my_app, storage: StorageMock
测试文件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" # 这里定义Mock的返回值 {:ok, %{body: "Some html with weather data"}} end) get(conn, "~p/api/users/validate") end end
我参考了Hammox文档和Phoenix控制器测试指南,尝试测试控制器端点。
当前配置存在问题,启动应用时出现如下错误:
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
我认为需要告知VMStorageMock是来自Hammox的模块,但找不到对应设置,请问我遗漏了什么配置?
问题核心是应用启动时StorageMock还未被定义——你在单个测试用例内部调用Hammox.defmock/2,但应用启动流程(包括supervisor初始化子进程)早于测试代码执行,此时VM找不到这个Mock模块。
具体解决步骤:
提前定义Mock模块
不要在测试用例里临时定义Mock,把Hammox.defmock/2放到测试目录的support文件中,比如新建test/support/mocks.ex:Hammox.defmock(StorageMock, for: MyApp.Storage)然后在
test/test_helper.exs中引入这个文件,确保所有测试启动前Mock模块已加载:# test/test_helper.exs ExUnit.start() Code.require_file("test/support/mocks.ex")检查应用启动的子进程配置
如果你的MyApp.Application里把storage配置的模块作为supervisor子进程启动,要注意:Hammox的Mock是纯函数模块,不是GenServer这类可启动的进程。如果原MyApp.Storage是需要启动的进程,要调整启动逻辑:- 生产环境启动真实的
Storage进程 - 测试环境不需要启动Mock进程,或者改用Hammox的进程Mock方式(适配GenServer类型的模块)
- 生产环境启动真实的
清理测试用例中的冗余代码
移除测试用例内部的Hammox.defmock/2调用,直接使用已提前定义好的StorageMock: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 expect(StorageMock, :get, fn args -> assert args == "Chicago" {:ok, %{body: "Some html with weather data"}} end) get(conn, "~p/api/users/validate") end end可选:配置Hammox全局参数
可以在config/test.exs中添加Hammox的全局配置,强化Mock的严格性:config :hammox, global: true, strict: true
内容的提问来源于stack exchange,提问作者Flame_Phoenix

