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.