Alexey's blog

OmniAuth-github acceptance testing

2 minute read Published: 2015-01-05

I was struggling with testing my toy app. In particular writing a feature spec that tests signing in with Github using Capybara and RSpec. I kept getting errors and couldn't figure out how to set up my test suite for this properly. So after spending the past two days googling helplessly I've finally accumulated enough information to piece it all together. It turned out to be a rather simple fix but I still decided to write it down. I'll provide the links to the posts I've found and write a summary of my thought process.

First of all, my app uses both Devise and Omniauth for authorization. A user can sign up through the site and also can sign in with their github account. I am new to this kind of setup, even though I am aware of how popular it is, but this was my first time setting it all up. I was just following the OmniAuth page on setting up some basic structure for this and everything was working just fine. My mistake was not going through every line of code and making sure I understand how every part works before moving on, but now I know better.

So two things I want to share, this is my Users::OmniauthCallbacksController:

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def github
    @user = User.from_omniauth(request.env["omniauth.auth"])
    session[:user_id] = @user.id

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication
      set_flash_message(:notice, :success, :kind => "Github") if is_navigational_format?
    else
      session["devise.github_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end

Notice that request.env["omniauth.auth"] is a hash that contains all the info about the user that we are authorizing with Github. And this is my from_omniauth method in my User model, or rather what it was like originally:

def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.name = auth.info.name
  end
end

With the help of this tumblr post I wrote the following spec:

require "rails_helper"

feature "User signs in" do
  before(:each) do
    OmniAuth.config.test_mode = true

    OmniAuth.config.mock_auth[:github] = {
      :provider => "github",
      :uid => "1234567",
      :credentials => {
        :token=> "3nkjnie"
      },
      :info => {
        :nickname => "test",
        :email => "[email protected]",
        :name => "Test User",
        :first_name => "Test",
        :last_name => "User",
        :location => "California",
        :verified => true
      }.stringify_keys!
    }.stringify_keys!
  end

  scenario "with github" do
    visit "/"

    click_link "Sign in with Github"
    expect(page).to have_content("Successfully authenticated from Github account")
  end
end

However, it was failing, and for a good reason. It couldn't access the returning hash information properly. The error was saying that there is no 'provider' method for Hash. And the problem was occurring inside the from_omniauth method.

So after thinking about this for some time and going through Thoughtbot's Hound source code for some tips on handling a user sign in from github I stumbled upon this construction:

def github_username
  request.env["omniauth.auth"]["info"]["nickname"]
end

And it made me realize that I could access the hash' info this way. Granted, this is not really some sort of revelation for a more experienced developer, but somehow I didn't think of this before. But after seeing it in action I decided to change the from_omniauth method into the following:

def self.from_omniauth(auth)
    where(provider: auth["provider"], uid: auth["uid"]).first_or_create do |user|
      user.email = auth["info"]["email"]
      user.password = Devise.friendly_token[0,20]
      user.name = auth["info"]["name"]
    end
  end

So basically changing the way that information is accessed from the Hash. And it actually worked! The test is passing and I am quite happy.