Testing Twitter with OmniAuth
There are a number of things I really love using to develop web applications:
- OmniAuth to allow users to utilize their internet drivers license.
- Devise is such a step forward in authentication systems.
- Cucumber is a no-brainer for testing.
What happened to me, though, is combining all three created a perfect storm of frustration and annoyance. Never has so little ruby code caused me so much suffering! I will attempt to help you navigate the Twitter/OmniAuth/Cucumber testing storm and avoid all my woes!
Removing the Frustration
What is extremely frustrating about testing authentication with the Twitter/OmniAuth combo is that it really is an extremely small amount of code and yet can potentially lead to hours of testing frustration. Once you know how to properly use OmniAuth in a mocked environment, it's really just a 1-2-3 punch. The problem lies in that the full and proper solution doesn't quite exist out on the internet right now. Part of this is due to Devise working in OmniAuth support and starting with OAuth2.0 (facebook) testing, the other part is that Twitter had the gall to force developers into using the https:// protocol very recently.
Step 1: Manually Trigger OAuth Callbacks
The first step is to make sure your Cucumber features / steps visit both the OmniAuth twitter provider page AND the callback page. In the context of your actual web application OmniAuth creates a route for "auth/:provider" that begins the OAuth authentication process and specifies a callback URL of "auth/:provider/callback". In order for authentication to complete you need to head off to twitter and return back. Since you aren't actually going to use Twitter, you need to manually kick off the callback URL.
Here are some sample step definitions for you:
#features/step_definitions/twitter_steps.rb
Given /^I am signed in$/ do
visit twitter_auth_path
# This is important!!!!! You must manually visit this page.
visit twitter_callback_path
end
When /^Twitter authorizes me$/ do
visit twitter_callback_path
end
Step 2: Faking out Twitter
Your test suite should be immune to both Twitter outages and local internet connectivity issues. In order to accomplish this, we're going to fake it's responses with FakeWeb. I'll just go ahead and reveal the whole shebang right here and now:
#features/support/fake_web.rb
require 'fake_web'
#I'm important for the secure get for verify_credentials
require 'net/https'
#During OAuthcalypse this base URL changed.
TWITTER_API_BASE = 'https://api.twitter.com/'
FakeWeb.allow_net_connect = false
FakeWeb.register_uri(:post, TWITTER_API_BASE + 'oauth/request_token',
:body => 'oauth_token=fake'
)
FakeWeb.register_uri(:post, TWITTER_API_BASE + 'oauth/access_token',
:body => 'oauth_token=fake&oauth_token_secret=fake'
)
FakeWeb.register_uri(:get,
TWITTER_API_BASE + '1/account/verify_credentials.json',
:body => File.join(Rails.root.to_s,
'features', 'fixtures', 'verify_credentials.json'
)
)
This is wee bit different than most of the other posts you'll see out there. For one, this occurred after the great OAuthcalypse, and thus the API endpoint for Twitter is the new and shiny one. Second, I'm manually requiring Net::HTTPS. GET requests seem to have a significant amount of problems using https:// if you don't manually include the library. The Net::HTTP library attempts to handle the request and bad things happen. You will see a fairly cryptic error message without manual inclusion of Net::HTTPS. Finally, you're looking for the body of the verify_credentials request, not the response.
In order to get yourself some credentials in JSON, all you need to do is head here. Twitter is always looking out for you. Remember to obfuscate some of that information if you are checking this into a public repository and you are using your personal Twitter account!
Step 3: Not Throwing Your Keyboard in a Fit of Rage
Now that you've gotten this far, let's recap what potential issues we've avoided:
- Our Cucumber steps manually visit the Twitter callback URL, forcing authorization.
- We have faked the relevant portions of the Twitter API.
- We have used the Twitter developers console to retrieve working account credentials
- We have corrected any quirkiness of the new use of https:// over http:// in Twitter authentication.
Now you should hopefully have no need to destroy animate or inanimate objects. I hope that you instead convert that energy into writing some flipping awesome code!