Acceptance tests for OmniAuth

One of the great gems released in the past few months was OmniAuth. It is very easy to use, it works without tons of configurations (unless configuring XML files is your thing) and there are already plenty of resources about it on the internet.

However, it is not easy to do acceptance tests with Omniauth as it depends on external services to work. So what should we do? When I face a scenario like this, I split the acceptance test in two parts: one before the external service and one after the external service response.

Testing the first one is trivial: you only have to ensure there is an <a> tag with href equals to “/auth/facebook” (or “/auth/#{insert_your_provider_here}” if you use another one). We don’t test any of the redirects done by OmniAuth internals, because it is already tested in gem’s tests, so we go to the next step: testing the OmniAuth callback.

Testing OmniAuth callbacks is in general cumbersome but for OAuth2 providers it is a bit easier as it uses Faraday internally to connect to the provider. With Faraday, we can configure a test adapter and stub calls to return what we want.

The OmniAuth strategy provides an entry point to the Faraday connection, but we don’t have an access to the strategy directly, so we need to store it globally. For a Facebook strategy, we can achieve it as below whenever configuring Omniauth middleware:

module OmniAuth
  mattr_accessor :facebook_strategy
end

middleware.use OmniAuth::Strategies::Facebook, "APP_ID", "APP_SECRET" do |strategy|
  OmniAuth.facebook_strategy = strategy
end

Note you will need OmniAuth 0.2.0 (which currently is OmniAuth master) as previous versions don’t yield a block on initialization like above.

With access to the strategy, we do the acceptance test. First, we define the hashes we will return on the Faraday responses.

FACEBOOK_INFO = {
  :id =>; '12345',
  :link => 'http://facebook.com/plataformatec',
  :email => 'myemail@example.com',
  :first_name => 'Plataforma',
  :last_name => 'Tecnologia',
  :website => 'http://blog.plataformatec.com.br'
}

ACCESS_TOKEN = {
  :access_token => "nevergonnagiveyouup"
}

Now, we will define the stubs. OAuth2 strategies do two requests: one to retrieve the access token and another to retrieve the user information. In this example, let’s stub the Facebook requests and assign these stubs to a new connection.

stubs = Faraday::Adapter::Test::Stubs.new do |b|
  b.post('/oauth/access_token') { [200, {}, ACCESS_TOKEN.to_json] }
  b.get('/me?access_token=nevergonnagiveyouup') { [200, {}, FACEBOOK_INFO.to_json] }
end

OmniAuth.facebook_strategy.client.connection = Faraday::Connection.new do |builder|
  builder.adapter :test, stubs
end

For each provider the URLs may differ, so an idea is to do this on a TDD way (or you can browse through the OmniAuth source code and see the url that it requests):

  1. Assign the Faraday fake connection without stubs.
  2. Run your test
  3. See the test to raise an exception like “No stubbed request for DESIRED_URL”.
  4. Add the stubbed request with the response that you want.
  5. Repeat this process until the test pass

This is what we do on acceptance tests with OmniAuth: testing before and after the access to the external services.

Another approach is to do only one test by short-circuiting the provider authentication URLs. To do that on a Rails application, you can store the provider URL on a method like “OmniAuth.facebook_url” and stub the method to return the callback URL on your test. If you happen to be using Devise, the upcoming 1.2 version does the short-circuiting automatically for you, as you can see in Devise integration tests.

What about you? How do you stub OmniAuth requests and responses on your applications?

  • http://www.google.com/profiles/myron.marston Myron Marston

    You may also want to checkout the Faraday middleware built in to VCR. It’s an alternative to the built-in test adapter. On the first test run, it’ll record the request and response, and then automatically use that to stub the responses for future test runs.

    https://github.com/myronmarston/vcr
    http://relishapp.com/myronmarston/vcr/v/1-4-0/dir/middleware/faraday-middleware

  • http://twitter.com/rlmflores Rodrigo Flores

    Looks like a great gem! I will test it :-).

  • http://twitter.com/sam_enspiral Samson Ootoowak

    I am using Devise 1.2.rc and Omniauth 0.2.0.beta1. I am also using Rspec-rails 2.4.1, Capybara 0.4.0 and Steak 1.0.1.

    Now that I got all that out of the way :) I was wondering if you had any insight on how one would leverage the short-circuit Devise provides for a Steak acceptance test.

  • http://blog.plataformatec.com.br/ josevalim

    You can check the Wiki pages for more information: https://github.com/plataformatec/devise/wiki/_pages (search for the Omniauth in it). If you still have questions, please try out Devise mailing list.