我想掌握正在通过架子测试测试的应用程序实例,以便我可以模拟其一些方法。我以为我可以简单地将应用程序实例保存在app
方法中,但是出于某些奇怪的原因,这不起作用。看来rack-test
只是使用实例获取类,然后创建自己的实例。
我已经进行了测试以证明我的问题(它需要宝石" sinatra"," rack-test"one_answers" rr"才能运行):
require "sinatra"
require "minitest/spec"
require "minitest/autorun"
require "rack/test"
require "rr"
describe "instantiated app" do
include Rack::Test::Methods
def app
cls = Class.new(Sinatra::Base) do
get "/foo" do
$instance_id = self.object_id
generate_response
end
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am a response"]
end
end
# Instantiate the actual class, and not a wrapped class made by Sinatra
@app = cls.new!
return @app
end
it "should have the same object id inside response handlers" do
get "/foo"
assert_equal $instance_id, @app.object_id,
"Expected object IDs to be the same"
end
it "should trigger mocked instance methods" do
mock(@app).generate_response {
[200, {"Content-Type" => "text/plain"}, "I am MOCKED"]
}
get "/foo"
assert_equal "I am MOCKED", last_response.body
end
end
rack-test
如何使用我提供的实例?我如何掌握rack-test
正在使用的实例,以便我可以模拟generate_response
方法?
更新
我已经取得了进步。事实证明,当提出第一个请求时,rack-test
即时创建了测试的实例(即get("/foo")
),因此在此之前不可能模拟App实例。
我已经使用RR的stub.proxy(...)
拦截.new
,.new!
和.allocate
;并添加了实例的类名和object_id
的puts语句。我还在测试类的构造函数和请求处理程序中添加了此类语句。
这是输出:
来自构造函数:< testSubject 47378836917780>代理拦截了新!实例:< testSubject 47378836917780>代理拦截了新实例:< sinatra ::包装纸47378838065200>从请求处理程序:< testSubject 47378838063980>
注意对象ID。经过测试的实例(从请求处理程序打印)从未通过.new
,也从未初始化。
因此,令人困惑的是,被测试的实例从未创建,但仍然存在一些。我的猜测是正在使用allocate
,但是代理截距表明它没有。我自己运行了TestSubject.allocate
来验证截距是否有效,并且确实有效。
我还向测试类添加了inherited
,included
,extended
和prepended
钩子并添加了打印语句,但从未被调用。这使我完全且完全困惑,因为在引擎盖下进行了哪种可怕的黑魔法架检验。
因此,总结:发送第一个请求时,在Fly 上创建了测试的实例。经过测试的实例是由Fel Magic创建的,并躲避了所有用钩子捕捉它的尝试,因此我找不到嘲笑它的方法。几乎感觉像rake-test
的作者已经延长了非凡的长度,以确保在测试过程中无法触摸应用程序实例。
我仍在四处寻找解决方案。
好吧,我终于得到了。
问题一直是Sinatra::Base.call
。在内部,它确实可以dup.call!(env)
。换句话说,每次运行call
时,Sinatra都会复制您的应用程序实例,并将请求发送给重复,并绕过所有模拟和存根。这解释了为什么没有触发生命周期的钩子,因为据推测dup
使用一些低级C魔术来克隆实例(需要引用)
rack-test
根本没有做任何令人费解的事情,它所称呼app()
以检索应用程序,然后在应用程序上调用.call(env)
。然后,我需要做的只是固执的我的班上的.call
方法,并确保在任何地方都不会插入Sinatra的魔法。我可以在应用程序上使用.new!
来阻止Sinatra插入包装纸和堆栈,并且我可以使用.call!
来调用我的应用,而无需重复我的应用程序实例。
注意:我不能再在app
函数内创建一个匿名类,因为每次调用app()
时都会创建一个新类,并且让我无法模拟它。
这是问题的测试,已更新到工作:
require "sinatra"
require "minitest/spec"
require "minitest/autorun"
require "rack/test"
require "rr"
describe "sinatra app" do
include Rack::Test::Methods
class TestSubject < Sinatra::Base
get "/foo" do
generate_response
end
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am a response"]
end
end
def app
return TestSubject
end
it "should trigger mocked instance methods" do
stub(TestSubject).call { |env|
instance = TestSubject.new!
mock(instance).generate_response {
[200, {"Content-Type" => "text/plain"}, "I am MOCKED"]
}
instance.call! env
}
get "/foo"
assert_equal "I am MOCKED", last_response.body
end
end
是的,机架测试每个请求实例化新的app
(可能是为了避免碰撞并以新鲜状态开始。)此处的选项是模拟Sinatra::Base
派生的类本身,在app
:
require "sinatra"
require "minitest/spec"
require "minitest/autorun"
require "rack/test"
require "rr"
describe "instantiated app" do
include Rack::Test::Methods
def app
Class.new(Sinatra::Base) do
get "/foo" do
generate_response
end
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am a response"]
end
end.prepend(Module.new do # ⇐ HERE
def generate_response
[200, {"Content-Type" => "text/plain"}, "I am MOCKED"]
end
end).new!
end
it "should trigger mocked instance methods" do
get "/foo"
assert_equal "I am MOCKED", last_response.body
end
end
或,整体模拟app
方法。