【问题标题】:Ruby on Rails: Overwrite instance variable on Selenium testsRuby on Rails:在 Selenium 测试中覆盖实例变量
【发布时间】:2026-01-01 11:50:01
【问题描述】:

我的控制器中有一个动作,它使用我无法控制的 API 中的对象实例加载实例变量:

def index
  @obj = MyObj.find(id: params[:id])
end

MyObj.find 进行 API 调用并将 API 返回的内容返回给我。

现在假设我想为我的视图编写一个测试,但我不能使用测试数据库,因为我的应用依赖于该 API。我永远不能指望 API 返回一个可测试的对象,而我想尝试的一些测试取决于该对象的状态。

我希望能够在渲染我的视图之前手动创建我自己的测试@obj,并让我的测试在由该@obj 通知的视图渲染上运行。

理想情况下是这样的:

before(:all) do
  @obj = {attr1: "abc", attr2: 123}
  driver.navigate.to("#{ENV['RAILS_HOST']}/my_view/123")
end

这显然行不通。有没有办法做到这一点?

编辑:尝试存根该方法似乎不起作用,这是它目前的样子:

规格:

before(:each) do
  allow(Library::Equipment)
    .to receive(:mymethod)
    .with(123)
    .and_return("Stub method")

  puts Library::Equipment.mymethod(123)
  #prints "Stub method"
  driver.navigate.to("#{ENV['RAILS_HOST']}/library/equipments/123/variables")
end

/library/equipments/:equipment_id/variables 路由到library/variables#index,如下所示:

def index
  @test = Library::Equipment.mymethod(123)
  puts @test
  #prints "Real method"
  # other code...
end

我的Library::Equipment 类有这个类方法:

def self.mymethod(param)
  "Real method"
end

在我的index.html.erb 中,我只需<%= @test %> 即可查看其中包含的内容。如您所见,mymethod 的返回值在从我的规范文件和我的 index 操作调用时有所不同

【问题讨论】:

  • 使用RSpec::Mocks.with_temporary_scope 将允许您在before(all) 中使用存根,但在作用域结束时会删除存根。请删除with_temporary_scope 并同时将before(:all) 更改为before(:each)/before
  • 结果是一样的,我已经编辑了问题以反映所做的更改。

标签: ruby-on-rails api selenium testing rspec


【解决方案1】:

您可以通过rspec's allow 方法对MyObj.find 的调用存根:

let(:obj) { { attr1: "abc", attr2: 123 } }
let(:obj_id) { 123 }

before(:each) do
  allow(MyObj)
    .to receive(:find)
    .with(id: obj_id)
    .and_return(obj)

  driver.navigate.to("#{ENV['RAILS_HOST']}/my_view/123")
end

allow 将为您提供模拟对方法调用的响应(例如find)并返回您选择的答案的可能性。这样,当您的控制器调用MyObj.find 时,不会调用find 的实际实现,而是调用返回通过and_return 指定的对象的rspec 代码。 with 方法仅用于缩小模拟范围,以便在参数不匹配时模拟不会响应。在您的情况下,它可能是可选的。

请注意,我将before(:all) 更改为before(:each)。前者只会在整个套件之前执行一次。由于您需要独立测试,因此您不希望更改 obj 会溢出到下一个测试。此外,似乎无法在 before(:all) 块中访问 let

鉴于 Selenium 测试通常是验收测试,但我会觉得这样的嘲讽很可疑。

如果您无法访问外部 API,则提供 canned responses 可能会令人讨厌,但至少被存根的层是清楚的。

【讨论】:

  • 我无法让它工作。我收到有关“让声明obj_idbefore(:context) 钩子中访问”的错误。另外,这种“允许”方法究竟是如何工作的?这是否意味着任何时候我调用 MyObj.find(id: 123) 都会返回该特定对象?这适用于我的控制器?
  • @bpromas 我详细说明了回答您的问题。
  • 这不起作用。在我的控制器操作中运行的find(由于我的driver.navigate.to 调用)正常运行并且找不到id = 123 的东西。但是,如果我在allow 方法下调用MyObj.find(id: 123),它会返回给我{ attr1: "abc", attr2: 123 },应该这样。两个问题:1)这里的 MyObj 是一个类,对吗? 2) 就我而言,MyObj 没有find 方法,但包含来自支持模型的方法。这会改变事情吗?
  • 是的,它确实改变了一些事情。模拟/存根时,您必须确定代码中要在执行代码和存根代码之间进行切割的位置。因此,您必须使模拟定义 (allow) 适应您的实际代码,这适用于调用方法的对象以及方法本身和参数。这就是为什么在与 Selenium 一起使用时使用 mocking 会产生异味:1)没有黑匣子发短信 2)相当脆弱。
  • 是的,它不起作用。如果我编写了一个我知道属于MyObj 的新方法,那么我在allow 中构建的方法不是在我的控制器中调用的方法,只有在我的规范文件中调用它才能得到返回我想从中得到。 ://