[英]How do I get the Sinatra app instance that's being tested by rack-test?
我想掌握通過機架測試測試的應用程序實例,以便可以模擬其某些方法。 我以為我可以將應用程序實例保存在app
方法中,但是由於某些奇怪的原因而無法正常工作。 似乎rack-test
只是使用實例來獲取類,然后創建自己的實例。
我已經進行了測試以證明我的問題(它需要運行“ sinatra”,“ rack-test”和“ 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
方法?
我沒有進步。 事實證明,在發出第一個請求(即get("/foo")
)時, rack-test
會即時創建被測試的實例,因此在此之前不可能模擬該應用程序實例。
我已經用rr的stub.proxy(...)
截獲.new
, .new!
和.allocate
; 並添加了帶有實例的類名和object_id
的puts語句。 我還在測試過的類的構造函數以及請求處理程序中添加了此類語句。
這是輸出:
From constructor: <TestSubject 47378836917780> Proxy intercepted new! instance: <TestSubject 47378836917780> Proxy intercepted new instance: <Sinatra::Wrapper 47378838065200> From request handler: <TestSubject 47378838063980>
注意對象ID。 測試的實例(從請求處理程序打印)從未經歷過.new
且從未初始化。
因此,令人困惑的是,從未創建被測試的實例,但無論如何仍然存在。 我的猜測是正在使用allocate
,但是代理攔截顯示它沒有使用。 我運行了TestSubject.allocate
來驗證攔截器是否正常工作。
我還向測試的類添加了inherited
, included
, extended
和prepended
鈎子,並添加了打印語句,但是它們從未被調用過。 這讓我完全無法理解到底要進行什么樣的可怕的黑魔法測試。
總結一下:發送第一個請求時, 即刻創建了測試實例。 被測試的實例是由惡魔魔術師創建的,並通過鈎子回避了試圖捕獲它的所有嘗試,因此我找不到模擬它的方法。 幾乎感覺就像rake-test
的作者費了rake-test
力氣才能確保在測試過程中不會碰到該應用實例。
我仍在摸索尋找解決方案。
好的,我終於明白了。
一直Sinatra::Base.call
,問題一直出在Sinatra::Base.call
。 在內部,它執行dup.call!(env)
。 換句話說,每次您運行call
,Sinatra都會復制您的應用程序實例,並將請求發送到重復的實例,從而繞開所有模擬和存根。 這說明了為什么沒有觸發生命周期掛鈎的原因,因為大概dup
使用了一些低級C魔術來克隆實例(需要引用)。
rack-test
根本不做任何令人費解的事情,它所做的全部只是調用app()
來檢索應用程序,然后在應用程序上調用.call(env)
。 然后,我需要做的就是在類中添加.call
方法,並確保Sinatra的魔力未插入任何地方。 我可以使用.new!
在我的應用程序上阻止Sinatra插入包裝器和堆棧,並且我可以使用.call!
在沒有Sinatra復制我的應用實例的情況下調用我的應用。
注意:我不再只能在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
(可能是避免沖突並以新狀態開始。)這里的選項是在app
內部模擬Sinatra::Base
派生類本身:
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
方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.