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

Ruby VCR测试无法捕获Sinatra内部HTTP调用的解决方法

问题

我开发了一个Sinatra应用,包含以下路由调用:

get '/test' do 
  @res = Net::HTTP.get_response(URI('http://www.iana.org/domains/reserved'))
  erb :test
end

对应的视图文件内容:

<%= @res.body %>

我使用Capybara + Selenium + VCR对该功能进行测试,测试可以通过,但VCR并未生成对应的cassette,无法实现测试提速。测试代码如下:

#!/usr/bin/env ruby

require 'test/unit'
require 'capybara'
require 'capybara/dsl'
require 'webdrivers/chromedriver'
require 'webmock'
require 'vcr'
require 'dotenv'
require_relative "../sinatra_app.rb"

class SinatraAppTest < Test::Unit::TestCase
  include Capybara::DSL
  
  def setup
    Dotenv.load "#{__dir__}/../.env"
    Capybara.register_driver :chrome do |app|
      Capybara::Selenium::Driver.new(app, :browser => :chrome, options: chrome_options)
    end
    Capybara.default_driver = :chrome
    Capybara.default_max_wait_time = 20
    Capybara.app_host = "http://localhost:9393"

    VCR.configure do |config|
      config.cassette_library_dir = "fixtures/vcr_cassettes2"
      config.hook_into :webmock
      config.allow_http_connections_when_no_cassette = true
      config.default_cassette_options = {re_record_interval: (3600 * (24 * 30))}
      config.ignore_localhost = true
    end
  end
  

  # Settings and profile for the Chrome Browser
  # NOTE: still cannot get headless working
  def chrome_options
    opts = Selenium::WebDriver::Chrome::Options.new
    # opts.add_argument('--headless') unless ENV['UI']
    opts.add_argument('--no-sandbox')
    opts.add_argument('--disable-gpu')
    opts.add_argument('--disable-dev-shm-usage')
    opts.add_argument('--window-size=1920,1080')
    opts
  end

  def test_iana
    VCR.use_cassette("iana") do
      visit "/test"
      assert page.has_content?("Example domains")
    end
  end

  def teardown
    Capybara.reset_sessions!
    Capybara.use_default_driver
  end
end

核心问题:如何捕获Sinatra应用内部发起的HTTP调用并通过VCR进行录制?(注:若将Net::HTTP调用放在测试代码内,VCR可正常录制,但这并非我期望的测试方式)

解决方案

原因分析

使用Selenium驱动时,Sinatra应用会以独立进程的形式启动(Chrome浏览器通过HTTP请求访问该进程),而VCR的钩子仅在测试进程中生效,无法捕获到Sinatra进程内部发起的外部HTTP请求。

解决方法

方法1:改用Capybara RackTest驱动(推荐)

RackTest驱动无需启动独立服务器,会直接在测试进程中调用Sinatra应用代码,因此VCR可以正常捕获应用内部的HTTP请求。

修改测试代码的setup方法,替换默认驱动为:rack_test:

def setup
  Dotenv.load "#{__dir__}/../.env"
  # 移除Chrome驱动注册,改用默认的RackTest驱动
  Capybara.default_driver = :rack_test
  Capybara.default_max_wait_time = 20
  # 无需设置app_host,RackTest直接调用应用实例

  VCR.configure do |config|
    config.cassette_library_dir = "fixtures/vcr_cassettes2"
    config.hook_into :webmock
    config.allow_http_connections_when_no_cassette = true
    config.default_cassette_options = {re_record_interval: (3600 * (24 * 30))}
    config.ignore_localhost = true
  end
end

方法2:保留Selenium驱动(需跨进程共享VCR配置)

如果必须使用Selenium(例如需要测试JavaScript交互),需要让Sinatra应用进程也加载VCR配置,确保请求能被捕获:

  1. 在Sinatra应用文件(sinatra_app.rb)中添加VCR和WebMock的配置:
require 'vcr'
require 'webmock'

VCR.configure do |config|
  config.cassette_library_dir = "fixtures/vcr_cassettes2"
  config.hook_into :webmock
  config.allow_http_connections_when_no_cassette = true
  config.default_cassette_options = {re_record_interval: (3600 * (24 * 30))}
end
  1. 在测试代码中,确保VCR的cassette使用支持跨进程的模式,例如添加:record => :new_episodes(按需调整):
def test_iana
  VCR.use_cassette("iana", :record => :new_episodes) do
    visit "/test"
    assert page.has_content?("Example domains")
  end
end

这种方法需要注意文件权限和cassette的读写冲突,建议仅在必须使用Selenium时采用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:39:56