如何在Rails 5与Minitest中创建Stub方法?订单创建打桩失败求助
看起来你遇到的核心问题是:被测代码里的order方法内部新建了一个完全独立的StraightServerKit::Client实例,而你测试中手动创建的client和这个内部实例毫无关系,所以之前的stub都没命中真实被调用的对象。下面给你两种可行的解决方案,以及分析你之前的错误原因。
正确方案1:使用Mocha(推荐)
Mocha的链式stub可以很方便地处理这种嵌套方法调用的场景,我们需要逐层mock:先让Client实例的orders方法返回一个mock对象,再让这个mock对象的create方法返回预设值。
require 'mocha/minitest' test "should create order without real API request" do # 1. 创建一个mock的orders对象,指定它的create方法行为 mock_orders = mock() # 可以用expects确保create被调用,或者用stubs只返回值 mock_orders.expects(:create) .with(instance_of(StraightServerKit::Order)) # 验证传入的参数类型 .returns("mock_order_response") # 预设返回值 # 2. 让所有Client实例调用orders方法时返回这个mock_orders StraightServerKit::Client.any_instance.stubs(:orders).returns(mock_orders) # 3. 执行被测请求 post post_order_path, params: { order: { amount: 10 } } assert_response :success end
正确方案2:使用Minitest原生Stub(无需额外gem)
如果你不想用Mocha,也可以用Minitest自带的stub_any_instance来实现:
require 'minitest/stub_any_instance' test "should create order with Minitest native stub" do # 1. 创建一个模拟的orders对象,实现create方法 mock_orders = Object.new def mock_orders.create(order_obj) # 这里可以根据需要返回任意预设数据 { id: "mock_123", amount: order_obj.amount } end # 2. 拦截Client所有实例的orders方法,返回我们的mock_orders StraightServerKit::Client.stub_any_instance(:orders, mock_orders) do # 3. 执行请求 post post_order_path, params: { order: { amount: 10 } } assert_response :success end end
为什么你之前的方法失败?
我们来逐个分析你踩的坑:
第一个测试(手动创建client后stub):
你测试里创建的client是独立的对象,而被测代码的order方法里会重新new一个全新的Client实例,这两个实例没有任何关联,所以你对这个手动client的stub根本不会被调用到。方法1(stub_any_instance用错):
stub_any_instance是类方法,你应该在StraightServerKit::Client类上调用,而不是在实例上。而且你要stub的是orders方法(因为代码里是client.orders.create),不是order或者new,所以调用方式完全错误。方法2(Mocha stub new实例方法):
new是类方法,不是实例方法,所以StraightServerKit::Client.any_instance.stubs(:new)完全无效。如果要stub Client的创建,应该用StraightServerKit::Client.stubs(:new).returns(mock_client),然后再给mock_client的orders方法打桩。方法3(stub_any_instance针对new):
new是类方法,stub_any_instance是用来stub实例方法的,所以这里会报错“undefined method `new' for class”,逻辑完全不对。
关键总结
- 打桩前一定要理清被测代码的对象创建流程,确保你stub的是实际被调用的那个对象的方法。
- 对于嵌套方法调用(
a.b.c),需要逐层打桩:先让a.b返回一个mock对象,再让这个mock对象的c方法返回预设值。 - 当被测代码内部新建实例时,用
any_instance(Mocha)或stub_any_instance(Minitest)来覆盖所有实例的方法,或者stub类的new方法返回预设的mock实例。
内容的提问来源于stack exchange,提问作者Jamesla

