Testing Twitter OAuth with real data

Ensure that your application’s Twitter integration works by having your test suite actually sign in every so often.

The most popular way to test Twitter OAuth is by having your test suite run against fake Twitter responses provided by gems like FakeWeb. The benefits of this approach are fast tests which never leave your network and predictable tests - once they're passing they'll never fail. Anyone will tell you that I love a fast test suite, which is why I built specjour but in the real world, Twitter is not very predictable. For my own sanity, my integration tests must reflect reality as close as possible by following the same path the user follows. So if the user needs to be signed in to Twitter in order to register for my application, my test suite needs to reflect that.

Enter EphemeralResponse

I built ephemeral_response to satisfy my need for fast tests that reflect reality. EphemeralResponse captures the response of the first request and returns that response for all subsequent requests until the cached response expires, 24 hours later by default. This means that you'll test against an external service with real data once a day. There are a handful of gems that do this web caching dance but ephermal_response has zero dependencies and is the simplest to set up, just call EphemeralResponse.activate:

require 'benchmark'
require 'ephemeral_response'

EphemeralResponse.activate # begin capturing responses

2.times do
  puts Benchmark.realtime { Net::HTTP.get "example.com", "/" }
end

1.44242906570435     # First request hits the server
0.000689029693603516 # Second request uses the cached response

Using Capybara to test Twitter OAuth

Our test depends on OmniAuth, Cucumber, Capybara, the capybara-mechanize driver, and ephemeral_response. We have to make real requests to Twitter which means we can't use capybara's rack-test driver. Thankfully, jeroenvandijk created capybara-mechanize which allows us to make external web requests while saving and sending cookies along the way.

First, we modify the remote? method in the capybara-mechanize driver so that we don't have to continually switch Capybara.app_host. While we're here, let's also set Capybara.default_host.

# features/support/capybara-mechanize.rb
Capybara.default_host = 'example.com'

class Capybara::Driver::Mechanize
  alias original_remote? remote?

  def remote?(url)
    uri = URI.parse(url)
    if uri.relative?
      last_request_remote? && remote_response.page.response['Location'] == url
    else
      (Capybara.app_host || Capybara.default_host) != uri.host
    end
  end
end

Now let's set up EphemeralResponse

# features/support/ephemeral_response.rb
EphemeralResponse.activate

Finally, the messy Cucumber step to sign in with Twitter. Refactor as necessary:

When /^I sign in$/ do
  Capybara.current_driver = :mechanize
  # Sign in to Twitter
  visit 'http://twitter.com/sessions/new'
  fill_in "Username or email", :with => 'my_app_test_account'
  fill_in "Password", :with => 'my_app_test_account_password'
  click_button 'Sign In'
  unless page.has_content?('Sign out')
    raise "Sign in failed! Manually sign in to reset the captcha"
  end

  # Sign in to app
  page.driver.get '/auth/twitter'
  page.driver.follow_redirect!

  if page.driver.response.redirect?
    page.driver.follow_redirect!
  else
    # Authorize the app on twitter
    click_button "Allow"
    click_link 'click here'
  end

  page.driver.follow_redirect!
  page.should have_content('Sign out')
end

That should be it. Run the step and check your spec/fixtures/ephemeral_response directory to see the cached web responses. Try disconnecting from the network and re-running the test, it should still pass.

Cool, we've covered the registration/sign in portion of the site by actually connecting to Twitter. Next time I'll show off my hypocrisy by faking Rails sessions so we can skip the entire OAuth/ephemeral_response dance when signing in throughout the rest of the test suite.

blog comments powered by Disqus
Stack of cucumber slices

Photo taken by maxmallett