Rails中使用Webmock Stub API请求无效,返回真实数据问题排查
问题原因及解决方案
核心问题
WebMock 只能拦截 Ruby 进程发起的 HTTP 请求,但你的 React 前端是在浏览器环境中运行的,点击按钮后通过 fetch 发起的请求是浏览器直接发送的,不属于 Ruby 进程的请求范畴,所以 WebMock 的 stub 规则完全没起作用,自然会拿到真实的 API 数据。
解决方案
方案一:使用 puffing-billy 拦截浏览器请求
puffing-billy 是专门用于拦截 Capybara 驱动中浏览器发起的 HTTP 请求的工具,能完美解决这个场景的问题:
- 安装依赖
在 Gemfile 的 test 组中添加:
group :test do gem 'puffing-billy' end
执行 bundle install 完成安装。
- 配置 Capybara 驱动
在spec/support/billy.rb中添加配置:
require 'billy/capybara/rspec' # 替换你当前使用的 JS 驱动,比如原来是 :selenium_chrome_headless,就改成 :selenium_chrome_headless_billy Capybara.javascript_driver = :selenium_billy
- 修改测试代码
用 Billy 的 stub 规则替代 WebMock:
require 'rails_helper' RSpec.describe 'visiting the embedded app', type: :system do it 'visits the embedded app' do # 用 Billy 拦截目标 API 请求并返回假数据 Billy.stub('api_endpoint') do |stub| stub.to_return( status: 200, body: { data: 'dummy' }.to_json, headers: { 'Content-Type' => 'application/json' } ) end visit 'my react page with the button' click_button "Call API" # 用 Capybara 内置的等待机制替代 sleep,确保请求完成后再断言 expect(page).to have_content('dummy') end end
方案二:调整前端请求指向本地代理(适合前端直接调用外部 API 的场景)
如果不想引入新 gem,可以修改前端在测试环境下的请求地址,让请求先经过 Rails 测试服务器,再由 Rails 用 WebMock 转发:
前端修改请求地址
将fetch("api_endpoint")改为fetch("/api/proxy")。新增 Rails 代理控制器
# app/controllers/api/proxy_controller.rb class Api::ProxyController < ApplicationController def index response = HTTP.get('真实的api_endpoint') render json: response.body, status: response.status end end
- 测试中用 WebMock stub 控制器的外部请求
RSpec.describe 'visiting the embedded app', type: :system do it 'visits the embedded app' do stub_request(:get, '真实的api_endpoint').to_return( body: { data: 'dummy' }.to_json, headers: { 'Content-Type' => 'application/json' } ) visit 'my react page with the button' click_button "Call API" expect(page).to have_content('dummy') end end
额外优化点
- 不要用
sleep 10等待请求完成,Capybara 会自动等待断言条件满足,硬编码 sleep 会让测试变慢且不稳定。
内容的提问来源于stack exchange,提问作者baggiogiacomo
相关产品推荐
相关产品推荐

