I want to get hold of the app instance being tested by rack-test so that I can mock some of its methods. I thought I could simply save the app instance in the app
method, but for some strange reason that doesn't work. It seems like rack-test
simply uses the instance to get the class, then creates its own instance.
I've made a test to demonstrate my issue (it requires the gems "sinatra", "rack-test" and "rr" to run):
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
How come rack-test
isn't using the instance I provided? How do I get hold of the instance that rack-test
is using, so that I can mock the generate_response
method?
Update
I have made no progress. It turns out rack-test
creates the tested instance on the fly when the first request is made (i.e. get("/foo")
), so it's not possible to mock the app instance before then.
I have used rr's stub.proxy(...)
to intercept .new
, .new!
and .allocate
; and added a puts statement with the instance's class name and object_id
. I have also added such statements in the tested class' constructor, as well as a request handler.
Here's the output:
From constructor: <TestSubject 47378836917780> Proxy intercepted new! instance: <TestSubject 47378836917780> Proxy intercepted new instance: <Sinatra::Wrapper 47378838065200> From request handler: <TestSubject 47378838063980>
Notice the object ids. The tested instance (printed from the request handler) never went through .new
and was never initialized.
So, confusingly, the instance being tested is never created, but somehow exists none the less. My guess was that allocate
was being used, but the proxy intercept shows that it doesn't. I ran TestSubject.allocate
myself to verify that the intercept works, and it does.
I also added the inherited
, included
, extended
and prepended
hooks to the tested class and added print statements, but they were never called. This leaves me completely and utterly stumped as to what kind of horrible black magic rack-test is up to under the hood.
So to summarize: the tested instance is created on the fly when the first request is sent. The tested instance is created by fel magic and dodges all attempts to catch it with a hook, so I can find no way to mock it. It almost feels like the author of rake-test
has gone to extraordinary lengths to make sure the app instance can't be touched during testing.
I am still fumbling around for a solution.