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
:
1 2 3 4 5 6 7 8 9 10 11 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 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
1 2 | # features/support/ephemeral_response.rb EphemeralResponse.activate |
Finally, the messy Cucumber step to sign in with Twitter. Refactor as necessary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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.