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

如何在RSpec中修改Stub实现请求先返回422再返回200

解决WebMock Stub覆盖问题:两次相同URL请求返回不同响应

你遇到的问题是WebMock的常见行为:当你定义多个匹配相同请求规则的stub时,最后定义的那个会覆盖前面所有的。所以你的save_payer_refund_fail_request(422)覆盖了save_payer_refund_success_request(200),导致两次请求都返回422,第二个断言自然失败。

下面给你几个可行的解决方案,按推荐程度排序:

方案1:使用序列响应(最简单直接)

WebMock允许给同一个stub定义一系列响应,每次请求会按顺序返回下一个结果。这样第一次请求返回422触发模态框,第二次返回200完成保存,完美匹配你的测试流程:

scenario 'successfully saves refund when user clicks yes continue on similar refund', js: true do 
  # 定义一个stub,先返回422,再返回200
  save_payer_refund_request = stub_request(:post, /remittances/)
    .to_return([
      { status: 422, body: {error: "SIMILAR_REMITTANCE"}.to_json },
      { status: 200 }
    ])

  visit record_refund_check_by_payer_remittances_path 
  # 填写表单的步骤...
  page.find_by_id('refund_record').click 
  wait_for_ajax 
  
  # 断言第一次请求已触发
  expect(save_payer_refund_request).to have_been_made.once
  expect(page).to have_content "A refund from this payer with this number with the amount of $#{@refundTotalAmount} and date of #{@EFTCheckDate} already exists in Value-Based Reimbursement. Are you sure you want to record this refund?" 

  page.find_by_id('save-refund-confirmation-button').click 
  wait_for_ajax 
  
  # 断言第二次请求已触发
  expect(save_payer_refund_request).to have_been_made.twice
  expect(page).to have_content "Refund check #{@checkNumber} to payer has been saved."
end

方案2:通过请求体区分两次请求(更精准)

如果两次POST请求的请求体内容不同(比如点击确认按钮时会额外发送confirm: true这类参数),你可以用with方法给两个stub设置不同的匹配条件,这样它们就不会互相覆盖:

scenario 'successfully saves refund when user clicks yes continue on similar refund', js: true do 
  # 匹配第一次请求(不带确认参数)
  save_payer_refund_fail_request = stub_request(:post, /remittances/)
    .with(body: ->(body) { 
      # 根据实际请求体逻辑调整,比如检查是否不含确认标识
      parsed_body = JSON.parse(body)
      !parsed_body.key?('confirm') || parsed_body['confirm'] != true
    })
    .to_return(status:422, body: {error: "SIMILAR_REMITTANCE"}.to_json) 

  # 匹配第二次请求(带确认参数)
  save_payer_refund_success_request = stub_request(:post, /remittances/)
    .with(body: ->(body) { 
      parsed_body = JSON.parse(body)
      parsed_body['confirm'] == true
    })
    .to_return(status: 200) 

  visit record_refund_check_by_payer_remittances_path 
  # 填写表单的步骤...
  page.find_by_id('refund_record').click 
  wait_for_ajax 
  
  expect(save_payer_refund_fail_request).to have_been_made
  expect(page).to have_content "A refund from this payer with this number with the amount of $#{@refundTotalAmount} and date of #{@EFTCheckDate} already exists in Value-Based Reimbursement. Are you sure you want to record this refund?" 

  page.find_by_id('save-refund-confirmation-button').click 
  wait_for_ajax 
  
  expect(save_payer_refund_success_request).to have_been_made
  expect(page).to have_content "Refund check #{@checkNumber} to payer has been saved."
end

方案3:动态响应(最灵活)

你还可以用lambda来动态决定返回的响应,比如根据请求次数或者请求内容返回不同结果:

scenario 'successfully saves refund when user clicks yes continue on similar refund', js: true do 
  save_payer_refund_request = stub_request(:post, /remittances/)
    .to_return do |request|
      # 根据已调用的次数返回不同响应
      if save_payer_refund_request.requests.size == 1
        { status: 422, body: {error: "SIMILAR_REMITTANCE"}.to_json }
      else
        { status: 200 }
      end
    end

  # 后续测试步骤和方案1一致...
end

为什么原来的代码会失败?

WebMock的stub匹配规则是最后定义的stub优先级最高,当两个stub匹配同一个请求(都是POST到/remittances),后面的stub会完全覆盖前面的,所以所有请求都会使用422的响应,导致第二个断言失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 09:18:17