Friday, October 9, 2009

Can't Stub Rack

‹prev | My Chain | next›

In my Rack application, I will have a private method that generates thumbnails. An RSpec example that describes this:
      it "should generate a thumbnail" do
app.should_receive(:mk_thumbnail)
get "/foo.jpg", :thumbnail => 1
end
In that example, app is the application defined for the benefit of Rack::Test:
def app
target_app = mock("Target Rack Application")
target_app.
stub!(:call).
and_return([200, { }, "Target app"])

Rack::ThumbNailer.new(target_app)
end
The example fails with:
1)
Spec::Mocks::MockExpectationError in 'ThumbNailer Accessing an image with thumbnail param should generate a thumbnail'
# expected :mk_thumbnail with (any args) once, but received it 0 times
./thumbnailer_spec.rb:44:

Finished in 0.015466 seconds

4 examples, 1 failure
To make that pass, I add the private method mk_thumbnail and a call to mk_thumbnail:
    def call(env)
req = Rack::Request.new(env)
if !req.params['thumbnail'].blank?
filename = "foo"
mk_thumbnail(filename, env)
thumbnail = ::File.new(filename).read
[200, { }, thumbnail]
else
@app.call(env)
end
end

private
def mk_thumbnail(filename, env)
end
That should make the example pass, but:
1)
Spec::Mocks::MockExpectationError in 'ThumbNailer Accessing an image with thumbnail param should generate a thumbnail'
# expected :mk_thumbnail with (any args) once, but received it 0 times
./thumbnailer_spec.rb:44:
Hunh? That totally should have been called!

After *much* experimentation, I finally conclude / guess that the app function must be getting evaluated in a different scope than the spec. Thus, when I place the expectation on the app in the code, I am placing it on a different app than Rack::Test is actually using. I must investigate this further, because I can see needing to do this often.

For tonight though, I side-step the issue, if only to make a little bit of progress. Rather than putting the expectation on an instance of the Rack::Thumbnailer class, I convert the method to a class method and put the expectation on the class itself:
      it "should generate a thumbnail" do
Rack::ThumbNailer.should_receive(:mk_thumbnail)
get "/foo.jpg", :thumbnail => 1
end
Updating the code so that this is a class method makes this example pass:
    def call(env)
req = Rack::Request.new(env)
if !req.params['thumbnail'].blank?
filename = "foo"
Thumbnailer.mk_thumbnail(filename, env)
thumbnail = ::File.new(filename).read
[200, { }, thumbnail]
else
@app.call(env)
end
end

private
def self.mk_thumbnail(filename, env)
end
I'll call it a night there and hope to make more progress tomorrow.

No comments:

Post a Comment