如何用MiniTest测试Rails父控制器before_action :require_subscription的触发逻辑
现有项目代码结构
所有订阅者模块下的控制器统一继承自Subscribers::BaseController,基类配置了带触发条件的订阅校验前置钩子:
class Subscribers::BaseController < ApplicationController before_action :require_subscription, if: :require_subscription? private def require_subscription RequireSubscriptionService.call(current_user) end def require_subscription? # 多维度触发条件判断逻辑 end end
以子类控制器Subscribers::EventsController为例,该控制器针对other_action动作配置了钩子跳过规则:
class Subscribers::EventsController < Subscribers::BaseController skip_before_action :require_subscription, only: :other_action def index end def show end def other_action end end
原有RSpec测试逻辑参考
过去使用RSpec时,通过实例方法接收校验的方式,直接验证钩子的触发行为:
describe 'Subscribers::EventsController' do describe '#index' do it '触发订阅校验钩子' do expect_any_instance_of(described_class).to receive(:require_subscription).and_return(:ok) get :index end end describe '#other_action' do it '跳过订阅校验钩子' do expect_any_instance_of(described_class).not_to receive(:require_subscription) get :other_action end end end
MiniTest下的测试实现
针对上述场景,有两种可落地的测试方案,可根据测试粒度选择:
方案1:静态校验回调配置
不需要发起真实请求,直接读取Rails控制器内置的回调链元数据,判断对应动作下回调的注册/跳过状态,执行效率更高,适合快速校验回调配置是否正确。
Rails的回调系统会将所有before_action/skip_before_action配置存储在_process_action_callbacks数组中,每个回调对象自带matches?方法,可自动匹配所有if/unless条件(包括skip_before_action配置的排除规则),判断当前动作上下文下是否会执行。测试代码示例:
require "test_helper" class Subscribers::EventsControllerTest < ActionDispatch::IntegrationTest setup do @controller = EventsController.new @controller.request = ActionDispatch::TestRequest.create @controller.response = ActionDispatch::TestResponse.create # 定位require_subscription对应的回调对象 @subscription_callback = EventsController._process_action_callbacks.find do |callback| callback.filter_key == :require_subscription end end test "index动作会触发require_subscription钩子" do @controller.params = { action: "index" } assert @subscription_callback.matches?(@controller) end test "other_action动作会跳过require_subscription钩子" do @controller.params = { action: "other_action" } refute @subscription_callback.matches?(@controller) end end方案2:桩测试覆盖真实请求链路
如果需要验证带条件的钩子在真实请求下的触发逻辑(比如校验require_subscription?的判断结果是否符合预期),可以使用MiniTest的方法桩能力,模拟方法调用行为,和原有RSpec测试逻辑完全对等。测试代码示例:
require "test_helper" class Subscribers::EventsControllerTest < ActionDispatch::IntegrationTest setup do @regular_user = users(:regular) sign_in @regular_user end test "满足触发条件时,index动作会执行require_subscription" do # 模拟满足触发条件的场景 EventsController.any_instance.stubs(:require_subscription?).returns(true) # 断言require_subscription会被调用1次 EventsController.any_instance.expects(:require_subscription).once get :index end test "other_action动作即使满足触发条件,也不会执行require_subscription" do EventsController.any_instance.stubs(:require_subscription?).returns(true) EventsController.any_instance.expects(:require_subscription).never get :other_action end end
注:如果使用Rails 7+默认的MiniTest配置,可通过
stub_any_instance方法替代Mocha的桩语法,不需要额外引入依赖即可完成方法模拟。
内容的提问来源于stack exchange,提问作者eugene_trebin

