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
相关产品推荐
相关产品推荐

