使用Rack::Test和Sinatra测试控制器实例变量

8
我有一个Sinatra应用程序,根据用户是否登录,可以将页面作为只读或可编辑的服务。
控制器设置一个变量@can_edit,由视图用于隐藏/显示编辑链接。如何在我的测试中测试@can_edit的值?我不知道如何在Rack::Test下访问控制器的当前实例。
我使用class_eval来存根控制器中的logged_in?方法,但我不得不查看last_response.body以查看我的编辑链接是否已设置@can_edit
如何直接测试@can_edit的值?
3个回答

8
很遗憾,我认为在不修改Rack::Test的情况下这是不可能的。在应用程序测试期间进行请求时,Rack::Test会执行以下操作:
  1. 将请求添加到最近请求的列表中
  2. 创建应用程序的新实例并调用其call方法
  3. 将您的应用程序响应添加到最近响应的列表中
访问last_requestlast_response很容易,但不幸的是,在运行应用程序时没有保存有关其状态的信息。
如果您有兴趣拼凑一个Rack::Test补丁来完成此操作,请从rack-test/lib/rack/mock_session.rb的第30行开始查看。这是Rack::Test运行您的应用程序并接收标准Rack应用程序返回值(状态、标头、正文)的地方。我的猜测是,您还需要修改应用程序,以收集和使其所有实例变量可访问。
无论如何,最好测试结果,而不是实现细节。如果要确保编辑链接不可见,请通过DOM id测试编辑链接的存在。
assert last_response.body.match(/<a href="..." id="...">/)

最好测试结果,而不是实现细节。我之前看到过类似的说法,但我不同意。如果您想要 2,1 + 1 的效果非常好,但是 1 + 2 + 10 - 11 也同样适用。这两种方法都不能证明您的应用程序“实际上正常工作”。 - nowk
1
我并不完全不同意你的看法,但是假设有一个方法实现不正确。如果该方法每次都返回正确答案(你想不到它失败的情况),那么这个实现是否真的“不正确”?这重要吗?如果你发现自己在测试方法内部的变量值,那么你可能没有足够的测试用例(输入)。如果你重命名变量或以其他方式更改应用程序的实现而不改变其行为,则你的测试应该无需修改即可通过。这是使用测试的主要原因之一。 - Alex Reisner
我认为测试@can_edit是在测试控制器的输出。我不喜欢通过检查视图来间接测试它,以确定@can_edit设置时应该发生什么。这应该是一个不同的测试,专门针对视图。 - Brian

4
通过一点小技巧是可以实现的。Sinatra应用程序的实例不可用,因为它们是在调用Sinatra::Base#call时创建的,就像Alex解释的那样。这个技巧会提前准备一个实例,并让下一个调用获取它。
require 'something/to/be/required'

class Sinatra::Base
  @@prepared = nil

  def self.onion_core
    onion = prototype
    loop do
      onion = onion.instance_variable_get('@app')
      return onion if onion.class == self || onion.nil?
    end
  end

  def self.prepare_instance
    @@prepared = onion_core
  end

  # Override
  def call(env)
    d = @@prepared || dup
    @@prepared = nil
    d.call!(env)
  end
end

describe 'An Sinatra app' do
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  it 'prepares an app instance on ahead' do
    app_instance = app.prepare_instance    
    get '/foo'
    app_instance.instance_variable_get('@can_edit').should be_true
  end
end

我找到了一种技巧,可以首先模拟运行当前测试的实例

这对我返回了nil的app_instance。可能是最新的Sinatra更改了其基类。 - gpavlidi

0
这里有一个不太好但可行的替代方案。
# app.rb - sets an instance variable for all routes
before do
  @foo = 'bar'
end

# spec.rb
it 'sets an instance variable via before filter' do
  my_app = MySinatraApplication
  expected_value = nil
  # define a fake route
  my_app.get '/before-filter-test' do
    # as previously stated, Sinatra app instance isn't avaiable until #call is performed
    expected_value = @foo
  end
  my_app.new.call({
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => '/before-filter-test',
    'rack.input' => StringIO.new
  })
  expect(expected_value).to eq('bar')
end

这使您能够针对Sinatra之前的过滤器进行测试,或访问为基本应用程序创建的实例变量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接