Testing External Redirects: VCR & Capybara-Mechanize

The problem:  You need to test an action in a Rails app that redirects to an external site or service.  For example, you want to do multipass-based single-sign on with Assistly.

You think, “Fine.  I’m using fakeweb, I’ll just create a stub for the external API.”  This works fine in theory, until you get the dreaded RoutingError.

The issue is that the default capybara driver uses Rack::Test, which very sanely routes every request to the “application under test.”  This is exactly what you want when you’re testing a self-contained app, but confusing as hell otherwise.

The Road Not Taken

When I was looking for a solution, I encountered a number of interesting and clever approaches (many about integrating with Braintree) that all ran into problems with our new-ish rails app:

  • Write a Rack middleware to handle the external request path, and proxy it, allowing a net stubber to take over.  I wasn’t able to get the “fake service” middleware to avoid the RoutingError without mucking about in config/environments.  That seemed baroque for a glorified test fixture.
  • sham_rack: a sinatra-based external service stubber that integrates with Net::HTTP.  Really clean, lightweight, but I couldn’t get it to bind just by defining it in features/support/.
  • Use a “real-browser” Capybara driver, like capybara-webkit or selenium.  Unfortunately, that just solves the RoutingError.  In order to actually stub the external server, enter even more scaffolding.

Mechanize to the Rescue

This capybara thread mentions using capybara-mechanize in passing, and then peters off into frustration.
Well, I was able to get capybara-mechanize (0.3.0.rc3) to play nicely with vcr in about 20 minutes.  It’s dead-simple to identify cucumber scenarios that are allowed to fetch external resources (by tagging them @mechanize), and vcr continues to work like a charm since capybara-mechanize handles requests within the same interpreter vcr has stubbed.
  @assistly @mechanize
  Scenario:  Assistly multipass
    Given an activated user exists with email: "a@b.com"
    When I go to the assistly multipass page
    And I sign in as the user
    Then I should be redirected to "subdomain.assistly.com" with a valid multipass token

It’s then possible to check that a redirect actually happened.

Then /^I should be redirected to "([^"]*)" with a valid multipass token$/ do |url|
  page.current_url.should =~ /#{url}\?multipass=(.*)$/
  MultiPass.decode(CONFIG[:site_key], CONFIG[:api_key], $1)
end

Since most Rack::Test goodies are available, it’s still easy to test the presence of cookies, or anything else hiding in the Rack request and response.

Getting capybara to end up on the redirect URL took some hacking of the VCR response.  Recording the normal Assistly interaction will include a redirect to subdomain.assistly.com, which I had to replace with a 200 response containing a page body.

I’m impressed at how quickly this plan came together, especially considering how complex all of the other options seemed to be.



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 369 other followers