Rack::Bug debugging toolbar in four minutes

Here’s a quick screencast demonstrating Rack::Bug, a new Rack middleware for inspecting requests to any Rack-compatible web application.

Download the screencast (12 MB, 4:00)

The source is on GitHub. It ships with nine panels:

  • Rails Info
  • Resource Usage
  • Rack Env
  • SQL
  • AR Objects
  • Memcached
  • Templates
  • Log
  • Memory Usage
./script/plugin install git://github.com/brynary/rack-bug.git
1
2
3
4
5
6
7
# config/initializers/middleware.rb
require "rack/bug"

ActionController::Dispatcher.middleware.use Rack::Bug,
  :ip_masks   => [IPAddr.new("127.0.0.1")],
  :secret_key => "epT5uCIchlsHCeR9dloOeAPG66PtHd9K8l0q9avitiaA/KUrY7DE52hD4yWY+8z1",
  :password   => "rack-bug-secret"

Webrat slides from GoGaRuCo

I finally got an internet connection (on the plane wifi) to upload my slide from GoGaRuCo 2009 about Webrat. Couldn’t get them up to SlideShare yet, so I’ll add that tomorrow. (Update: Slides are now up on SlideShare and embedded below.)

Thanks again to Josh and Leah for organizing GoGaRuCo. I had a great time in SF meeting a lot of cool people and learning about all sorts of new technologies I want to try out soon.

Download the slides as a PDF (1.9 MB)

Note: The talks were recorded, so a full video of my presentation should be online soon.

Switching Webrat to Selenium mode

Ryan Bates did a great job covering Webrat in Railscast #156, and I’d recommend anyone who hasn’t used Webrat check it out. It’s a clear demonstration of how you can start using Webrat in a few minutes today with Rails built-in integration testing framework. Webrat works with RSpec, Cucumber, Shoulda, etc. too, but they aren’t required.

Unfortunately, during the screencast some Webrat bugs prevented running the tests in Selenium mode. Webrat has worked with Selenium for some time, but there have been some lingering rough edges around getting it up and running that I’ve been meaning to resolve. Today I’m happy to report those issues are now fixed, and getting started using Selenium with Webrat should now be drop-dead simple.

Give it a try by checking out the full Episode #156 source code, then make the following changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# config/environments/test.rb
config.gem "webrat", :version => ">=0.4.4" # was 0.4.3

# test/test_helper.rb
class ActiveSupport::TestCase
  # ...
  self.use_transactional_fixtures = false # was true

  # ...

  # Add these lines:
  setup do |session|
    session.host! "localhost:3001"
  end
end
  
Webrat.configure do |config|
  config.mode = :selenium # was :rails
end

Running the test again should pop open a Firefox window for the running tests and print output like this:

$ rake test:integration
(in /Users/bhelmkamp/p/railscasts-episodes/episode-156/authenticator)
Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
Started
==> Waiting for Selenium RC server on port 4444... Ready!
==> Waiting for rails application server on port 3001... Ready!
..
Finished in 11.679288 seconds.

2 tests, 2 assertions, 0 failures, 0 errors

Success!

Note: I just pushed Webrat 0.4.4, so it might not be available on all the gem sever mirrors yet. If not, you can install it with the following commands:

git clone git://github.com/brynary/webrat.git
cd webrat
rake install_gem

Speaking about Webrat at GoGaRuCo

Thanks to all those kind attendees who voted for my talk proposal, I’ve been invited to speak at GoGaRuCo in San Francisco on April 17-18th. My talk is titled Webrat: Rails Acceptance Testing Evolved, and I’m especially excited for two reasons:

  • The conference looks like it will be excellent. There are lots of talks I’m looking forward to seeing, Josh and Leah are doing a great job as organizers (harder than it looks!), and it’s in a cool city I haven’t visited before.
  • This is the first time I’ll be giving a talk about Webrat specifically, rather than covering Webrat as part of a broader topic. The project is almost a year and a half old now, and there’s plenty of interesting bits to talk about these days.

The conference is sold out, but if you’re going you should say hello. I’m always up for chatting about Ruby, scaling or testing, and grabbing a beer. Should be easy to find, and you can message me as @brynary on Twitter.

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.

Cucumber step definition tip: Stubbing time

When writing Cucumber scenarios, you'll occasionally need to describe a behavior that is dependent on time passing. For example, at weplay we want to ensure that a password reset link expires after one week.

In a RSpec code example, you might be used to stubbing time in this situation:

1
2
3

now = Time.now
Time.stub!(:now).and_return(now + 8.days)

This doesn't work out-of-the-box in a Cucumber step definition. By default, Cucumber doesn't include any of RSpec's mocking or stubbing support. This makes sense because Cucumber is a tool for acceptance testing (full stack), where stubbing and especially mocking should be discouraged and only used for very specific purposes (like stubbing time!).

If you find yourself in this situation, you can enable RSpec's stubbing support in your Cucumber step definitions by adding the following code to env.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

require "spec/mocks"

Before do
  $rspec_mocks ||= Spec::Mocks::Space.new
end

After do
  begin
    $rspec_mocks.verify_all
  ensure
    $rspec_mocks.reset_all
  end
end

Now you can go ahead and create a time_steps.rb like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

When /^(\d+) minutes pass$/ do |minutes|
  now = Time.now
  Time.stub!(:now).and_return(now + minutes.to_i.minutes)
end

When /^(\d+) hours pass$/ do |hours|
  now = Time.now
  Time.stub!(:now).and_return(now + hours.to_i.hours)
end

When /^(\d+) days pass$/ do |days|
  now = Time.now
  Time.stub!(:now).and_return(now + days.to_i.days)
end

RSpec will clear your time stub at the end of each scenario.

The RSpec Book is now in Beta

The RSpec Book

I’m very happy to report that The RSpec Book, which I’ve been collaborating on, is now available for sale as a Beta Book from the Pragmatic Bookshelf. David Chelimsky posted an announcement to the rspec-users mailing list which sums it up quite well. To quote:

On behalf of all the authors, I’d like to extend a special thank you to all of you who have contributed to the software and the conversation around RSpec, Cucumber, and BDD in general. RSpec would be nothing without the community that has evolved around it, so thank you, thank you, thank you.

Enjoy! And if you do pick it up, please let us know what you think. We’re eager to improve it based on the feedback we get.