You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MIX_ENV=test环境下无法启动Elixir桌面应用问题排查

问题描述

背景

我有一个Phoenix伞形应用,MIX_ENV=prod环境下能正常运行,但在MIX_ENV=dev执行mix test时会报错。

报错信息

18:39:42.101 [error] ** wx object server {:local, WebInterface} terminating
** Last message in was {:"$gen_cast", {:show, &WebInterface.Endpoint.url/0}}
** When Server state == %Desktop.Window{
  module: nil,
  taskbar: nil,
  frame: {:wx_ref, 35, :wxFrame, []},
  notifications: %{},
  webview: {:wx_ref, 49, :wxWebView, []},
  home_url: &WebInterface.Endpoint.url/0,
  last_url: nil,
  title: "Market Manager",
  rebuild: 0,
  rebuild_timer: {:interval, #Reference<0.2679771817.2635857922.255366>}
}
** Reason for termination ==
** {:badarg,
 [
   {:erlang, :binary_to_existing_atom,
    ["Elixir.WebInterface.Endpoint.HTTP", :utf8],
    [error_info: %{module: :erl_erts_errors}]},
   {:elixir_aliases, :safe_concat, 1,
    [file: ~c"src/elixir_aliases.erl", line: 139]},
   {WebInterface.Endpoint, :url, 0,
    [file: ~c"lib/web_interface/endpoint.ex", line: 4]},
   {Desktop.Window, :prepare_url, 1,
    [file: ~c"lib/desktop/window.ex", line: 666]},
   {Desktop.Window, :handle_cast, 2,
    [file: ~c"lib/desktop/window.ex", line: 604]},
   {:wx_object, :handle_msg, 5, [file: ~c"wx_object.erl", line: 493]},
   {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 241]}
 ]}

问题代码片段

问题出在application.ex的启动代码中:

@impl true
def start(_type, _args) do
  children = [
    Telemetry,
    {Phoenix.PubSub, name: PubSub},
    Endpoint,
    Manager,
    %{
      id: Desktop.Window,
      start:
        {Desktop.Window, :start_link,
         [
           [
             app: :web_interface,
             id: WebInterface,
             title: "Market Manager",
             size: WindowUtils.calculate_window_size(0.6, 0.8),
             menubar: MenuBar,
             icon: "static/images/resized_logo_5_32x32.png",
             url: &WebInterface.Endpoint.url/0    # 疑似问题行
           ]
         ]},
      restart: :transient,
      shutdown: 5_000
    }
  ]

  opts = [strategy: :one_for_one, name: WebInterface.Supervisor]

  Supervisor.start_link(children, opts)
end

已尝试的配置修改

修改了config/test.exs:

import Config

config :web_interface, WebInterface.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 4002],
  secret_key_base: "some_key",
  server: true,
  check_origin: false

生产环境config/prod.exs配置:

config :web_interface, WebInterface.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 80],
  cache_static_manifest: "priv/static/cache_manifest.json",
  secret_key_base: "some_key",
  check_origin: false,
  server: true

疑问

  1. 问题产生的原因是什么?
  2. 如何解决这个问题?

问题解答

原因分析

报错栈里的{:erlang, :binary_to_existing_atom, ["Elixir.WebInterface.Endpoint.HTTP", :utf8]}是核心问题:WebInterface.Endpoint.url/0生成URL时,会尝试将HTTP适配器名称转为已存在的原子,但测试环境下Phoenix的HTTP适配器进程还没完全初始化,对应的原子Elixir.WebInterface.Endpoint.HTTP尚未创建,导致binary_to_existing_atom调用失败抛出badarg错误。

另外,测试环境完全不需要启动Desktop.Window这类GUI组件,强行启动不仅会引发初始化顺序问题,还会额外消耗资源。

解决方案

推荐两种可行方式:

方案一:根据环境条件启动Desktop.Window

修改application.ex的子进程列表,仅在非测试环境加载GUI组件:

@impl true
def start(_type, _args) do
  children = [
    Telemetry,
    {Phoenix.PubSub, name: PubSub},
    Endpoint,
    Manager
    # 仅在非测试环境添加Desktop.Window
    | if Mix.env() != :test do
        [
          %{
            id: Desktop.Window,
            start:
              {Desktop.Window, :start_link,
               [
                 [
                   app: :web_interface,
                   id: WebInterface,
                   title: "Market Manager",
                   size: WindowUtils.calculate_window_size(0.6, 0.8),
                   menubar: MenuBar,
                   icon: "static/images/resized_logo_5_32x32.png",
                   url: &WebInterface.Endpoint.url/0
                 ]
               ]},
            restart: :transient,
            shutdown: 5_000
          }
        ]
      else
        []
      end
  ]

  opts = [strategy: :one_for_one, name: WebInterface.Supervisor]

  Supervisor.start_link(children, opts)
end

方案二:确保Endpoint完全初始化后再启动Desktop.Window

如果测试环境确实需要GUI组件,可调整Supervisor启动逻辑保证依赖顺序:

  1. 给Desktop.Window添加依赖声明,确保它在Endpoint启动完成后再启动:
%{
  id: Desktop.Window,
  start: {Desktop.Window, :start_link, [...]},
  restart: :transient,
  shutdown: 5_000,
  dependencies: [Endpoint] # 添加依赖关系
}
  1. 或者将Supervisor策略改为:rest_for_one,保证前面的进程启动成功后再启动后续进程:
opts = [strategy: :rest_for_one, name: WebInterface.Supervisor]

优先推荐方案一,测试环境无需加载GUI组件,既能避免初始化问题,还能加快测试启动速度。

内容的提问来源于stack exchange,提问作者Flame_Phoenix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 10:26:09