Tonight I am giving Sinatra 1.0 a try. For the first part of my new chain, I planned to upgrade EEE Cooks to CouchDB 0.10, couchdb-lucene 0.5, and Sinatra 1.0.
I tackled CouchDB and couchdb-lucene first because I expected them to be the riskiest (most likely to fail, require significant changes or just take really long). It is always best to get risky things out of the way early. Leaving them to the end invites wasted effort.
Sinatra 1.0 is not quite out yet, but the pre-release is available. To get that installed, I use the pre-release feature of rubygems 1.3.5 / gemcutter:
cstrom@whitefall:~/repos/eee-code$ gem install --pre sinatraTo try it out, I run my Cucumber scenarios:
WARNING: Installing to ~/.gem since /var/lib/gems/1.8 and
/var/lib/gems/1.8/bin aren't both writable.
Successfully installed sinatra-1.0.a
1 gem installed
cstrom@whitefall:~/repos/eee-code$ cucumberDang it! It just worked. So what now? I really thought that would take up more time. Stupid backward compatibility.
...
39 scenarios (1 pending, 38 passed)
344 steps (1 pending, 343 passed)
2m29.522s
After fixing a few stray unit tests / specs, I decide now might be a good time to revisit a problem that I experienced with Rack::Test last year. Specifically, I could not figure out how to stub an instance of a Rack application. Maybe updated versions of Rack and Rack::Test will get me past this.
Rack applications create normal ruby instances. Those instances must respond to
call
with a standard Rack response. There is no reason that these instances cannot be treated like any other object when testing. Except Rack::Test requires an "app" method, which complicates things:def appGetting to the
target_app = mock("Target Rack Application", :call => [200, { }, "Target app"])
Rack::ThumbNailer.new(target_app)
end
Rack::Thumbnailer
instance in order to set expectations or stub out method calls proved beyond me.The specific problem that I was trying to solve last year was middleware that could create thumbnail images. It was a fairly easy problem that I was able to solve, but I was not able to easily test that solution. Specifically, there were two private methods that I wanted to stub—one that pulls the images from the target application, the other that actually makes the thumbnail.
The Rack class looks something like this:
module RackWhen testing, I mostly do not care what the
class ThumbNailer
DEFAULT_CACHE_DIR = '/var/cache/rack/thumbnails'
def initialize(app, options = { })
@app = app
@options = {:cache_dir => DEFAULT_CACHE_DIR}.merge(options)
end
def call(env)
req = Rack::Request.new(env)
if !req.params['thumbnail'].blank?
filename = @options[:cache_dir] + req.path_info
unless ::File.exists?(filename)
image = rack_image(@app, env)
mk_thumbnail(filename, image)
end
thumbnail = ::File.new(filename).read
[200, { }, thumbnail]
else
@app.call(env)
end
end
private
def rack_image(app, env)
# Code to extract the image from the target application (e.g. Sinatra)
end
def mk_thumbnail(filename, image_data)
# Code to make thumbnails
end
end
end
rack_image
and mk_thumbnail
private methods are doing. They are small and easily testable, but most of my tests do not need to actually perform those actions—it is enough simply that they are called. Hence the need to stub them out when testing other things (e.g. that thumbnails are not generated if they are already cached).It turns out to be fairly trivial to accomplish this. In the
app
method of the spec, I memoize the rack application:def appThat allows me to stub methods or set expectations on my rack instance in RSpec examples:
target_app = mock("Target Rack Application", :call => [200, { }, "Target app"])
@app ||= Rack::ThumbNailer.new(target_app)
end
it "should generate a thumbnail" doLast time around I had to hack something together with class methods. Being able to set expectations directly on my Rack object makes for much clearer intent.
app.
should_receive(:mk_thumbnail).
with("/var/cache/rack/thumbnails/foo.jpg", "image data")
get "/foo.jpg", :thumbnail => 1
end
I am not sure if I tried memoizing like this when I first attempted this or if this is only possible with more recent versions of the various gems involved. This seems like something that should have worked even back then. It also seems like something I would have tried. I am not sure if I can recreate my old setup to verify this. I will not bother though, I am content to to know that I can do this now and in the future.
Day #11
Hi Chris,
ReplyDeleteThanks for this article.
This is off topic, but what software do you use to post your code hight line. Can you share it with me?
Thanks,
Luan
Luan: I use http://code.google.com/p/google-code-prettify/. You can view the page source of my blog to see how I get it to work on blogspot.
ReplyDeleteYou might also try embedding gists from github. Those work nicely for most folks.
Thanks a lot for your quick reply.
ReplyDelete