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

Rails Mailgun中间件RSpec测试报merge!方法不存在错误

问题描述

为Mailgun API开发Rails/Rack中间件时,使用完全一致的请求参数在Postman中测试连接正常,但运行RSpec用例时在Failure/Error: @app.call(env)位置抛出如下错误:

NoMethodError:
  undefined method `merge!' for #<Mailgun::Tracking::MailLogger:0x0000557e6bc8bb50>

自定义代码中从未调用过任何merge相关方法,无法定位错误来源。已按照项目使用的Mailgun Tracking gem官方指引,在config/initializers目录完成api_key与endpoint的初始化配置,开发仅涉及两个核心文件:

相关代码

1. MailLogger中间件(归属Mailgun::Tracking模块,已注册为应用中间件)

require 'logger'
require 'mailgun/tracking'
require 'rack/contrib'
require 'rack'
require 'byebug'

module Mailgun::Tracking
  class MailLogger
    def initialize(app, options = {
    "HTTP_X_MAILGUN_API_KEY" => ENV['MAILGUN_API_KEY'],
    "HTTP_X_MAILGUN_ENDPOINT" => ENV['MAILGUN_ENDPOINT']
    })
    @app = app
    @options = options
    end

    def call(env)
    @app.call(env)
    end
  end 
end

2. RSpec测试代码

require 'logger'
require "json"
require "rails_helper"
Bundler.require(*Rails.groups)
require_relative "../../lib/middleware/mailgun/mailgun_tracking.rb"

RSpec.describe Mailgun::Tracking::MailLogger, type: 'Mailgun Webhook' do
  subject(:response) { app.call(env) }
  # env to pass in middleware, url endpoint & key
  let(:app) { described_class.new(Rails.application) }
  let(:env) { described_class.new(app, options = {
   "HTTP_X_MAILGUN_API_KEY" => ENV['MAILGUN_API_KEY'],
    "HTTP_X_MAILGUN_ENDPOINT" => ENV['MAILGUN_ENDPOINT']
  }) }

 it "returns a 200 status code" do
    expect(response).to eq([200, {}, ["OK"]])
  end
end

测试使用的请求头(api_key)、端点(events)与Postman中正常通行的参数完全一致,仅期望请求返回200 OK响应,但始终抛出上述找不到merge!方法的异常。

错误原因
  • 核心问题是测试代码中env的定义完全违反Rack规范:Rack中间件call方法接收的env参数必须是Hash类型的Rack环境对象,但当前代码把env赋值为MailLogger类的实例。
  • 执行@app.call(env)时,下游Rack中间件/Rails应用会对传入的env对象调用merge!方法修改请求环境,由于传入的是MailLogger实例,该类没有实现merge!方法,直接触发NoMethodError。
  • 额外代码瑕疵:let(:env)块中写的options = {}是无意义的局部变量赋值,完全不参与Rack环境构造。
修复方案

修正测试中的env构造逻辑,使用Rack提供的MockRequest.env_for方法生成合法的Hash类型环境对象,不要把中间件实例当成env传入,单测场景下可以直接用lambda mock下游应用,不需要依赖完整Rails应用:

RSpec.describe Mailgun::Tracking::MailLogger, type: 'Mailgun Webhook' do
  # Mock下游Rack应用,返回预期的200响应
  let(:mock_app) { ->(env) { [200, {}, ["OK"]] } }
  subject(:middleware) { described_class.new(mock_app) }

  # 构造合法的Rack环境对象
  def build_env(path, custom_opts = {})
    Rack::MockRequest.env_for(path, {
      "HTTP_X_MAILGUN_API_KEY" => ENV['MAILGUN_API_KEY'],
      "HTTP_X_MAILGUN_ENDPOINT" => ENV['MAILGUN_ENDPOINT']
    }.merge(custom_opts))
  end

  it "returns a 200 status code" do
    response = middleware.call(build_env("/mailgun/events"))
    expect(response[0]).to eq(200)
  end
end

内容的提问来源于stack exchange,提问作者Paul Anthony McGowan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 23:39:36