Rack::Test released: Simply test any Rack-compatible app

Today I’m happy to announce the first release of a new library I’ve been cooking up: Rack::Test. It’s a simple API for issuing fake requests to any Rack app (e.g. Rails, Sinatra, Merb, etc.) similar to what you might be familiar with in Rails’ integration tests or Merb’s request specs. Basically, I extracted Merb’s request helper code into a small, reusable, framework agnostic library. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require "rack/test"

class HomepageTest < Test::Unit::TestCase
  include Rack::Test::Methods
  
  def app
    MyApp.new
  end
  
  def test_redirect_logged_in_users_to_dashboard
    authorize "bryan", "secret"
    get "/"
    follow_redirect!
    
    assert_equal "http://example.org/redirected", last_request.url
    assert last_response.ok?
  end
end

While developing Webrat, I started to realize all of the Ruby web frameworks were reinventing boilerplate code to create a test environment for issuing fake requests. They all have to:

  • Convert params hashes into encoded strings (something the browser handles for normal requests)
  • Maintain a simulated cooke jar to enable sessions
  • Generate multipart requests (to support test file uploads)

Through discussions with Josh Knowles and Yehuda Katz, it became apparent that an abstracted layer for these concerns would be valuable.

With Rack::Test, we hope to make it easy for frameworks to encourage their users to write tests by making it trivial to provide a testing environment. We’d like to foster compatibility between Ruby web app testing environments (especially important as ideas like multi-framework apps become more prominent). The philosophy is the library should stay small and extendable so frameworks can layer on additional functionality they want to offer without modifying Rack::Test’s core behavior or resorting to monkeypatching.

The Rack::Test API

The full RDoc is available on gitrdoc, but I’ve reproduced some of it below. It should feel familiar if you’re used to Rails integration testing or Merb request specs:

Rack::Test.initialize(app) — Initialize a new session for the given Rack app

#get(uri, params = {}, env = {}) — Issue a GET request for the given URI with the given params and Rack environment. Stores the issues request object in #last_request and the app’s response in #last_response. Yield #last_response to a block if given.

#post, #put, #delete, and #head — Behave like #get but specifying a different HTTP method

#request(uri, env = {}) — Issue a request to the Rack app for the given URI and optional Rack environment. Stores the issues request object in #last_request and the app’s response in #last_response. Yield #last_response to a block if given.

#follow_redirect! — Rack::Test does not follow any redirects automatically. This method will follow the redirect returned in the last response. If the last response was not a redirect, an error will be raised.

#header(name, value) — Set a header to be included on all subsequent requests through the session. Use a value of nil to remove a previously configured header.

#authorize(username, password)— Set the username and password for HTTP Basic authorization, to be included in subsequent requests in the HTTP_AUTHORIZATION header.

#last_request — Return the last request issued in the session. Raises an error if no requests have been sent yet.

#last_response — Return the last response received in the session. Raises an error if no requests have been sent yet.

Note: Thanks to the people who helped get Rack::Test off the ground: The Merb Team for their request helper code; Yehuda Katz and Josh Knowles for guidance, encouragement and support; Simon Rozet and Pat Nakajima for early code contributions.

12 Responses to “Rack::Test released: Simply test any Rack-compatible app”

  1. # Dr Nic Says:

    This is wonderful news; awesome.

  2. # bryanl Says:

    Bryan… this looks very cool.

  3. # Avdi Says:

    This looks very useful!

    Can you briefly sum up how this differs from the Rack support built into recent Webrat versions?

  4. # Ryan Sobol Says:

    Dyno-mite! #last_request and #last_response look handy.

  5. # Bob Aman Says:

    Very, very cool. Anyone feel like doing the equivalent for RSpec?

  6. # Pelle Says:

    Wow this looks immediately useful in so many ways.

    @Bpb Aman, it is just a module that you include. It works equally well with RSpec as far as I can see.

    http://github.com/brynary/rack-test/blob/f79784993ca4c6ff7e0fa4aaca36b47881af2d62/spec/rack/test_spec.rb

  7. # nap Says:

    Exactly what I needed. Thanks. And yep, works nicely with RSpec.

  8. # Bob Aman Says:

    @Pelle

    Oh, nice, didn’t actually look at the code, just what was in the post. My bad.

  9. # Arnaud Meuret Says:

    Great news. As a reader of your beta book which I strongly recommend I can only ask how this integrates with Webrat and if it will bring it for free with all rack-based frameworks, like Ramaze for instance ?

  10. # Alex Chaffee Says:

    Great work!

    A few suggestions:

    • Rename Rack::Test::Session to Rack::Test::Conversation to avoid confusion with the concept of cookie-based rack sessions
    • Once you’ve done that, provide access to rack sessions via
      def session
        last_request.env['rack.session']
      end
    
    • Rename “last_request” and “last_response” to “request” and “response”—the “last” is redundant since there’s usually no other requests or responses that it could be.

    You can easily overload the existing “request” method like this:

        def request(*args)
          args.empty? ? last_request : rack_test_session.request(*args)
        end
    
  11. # Yuumi Yoshida Says:

    Thanks!

    I can wrote RSpec code for Rails Metal :-)

    http://d.hatena.ne.jp/yuum3/20090427/1240796189 (Japanese)

  12. # Dan Pickett Says:

    This gem is awesome! I was relying on the Rails stack to get some of my acceptance tests working. I now have it completely decoupled.

    Thanks so much for putting it together!