7

Following the Railscast on Devise and OmniAuth I have implemented an OmniauthCallbacksController < Devise::OmniauthCallbacksController which contains a single method to handle an OmniAuth callback:

def all
  user = User.from_omniauth(request.env["omniauth.auth"])
  if user.persisted?
    sign_in_and_redirect user
  else
    session["devise.user_attributes"] = user.attributes
    redirect_to new_user_registration_url
  end
end
alias_method :facebook, :all

routes.rb:

devise_for :users, controllers: {omniauth_callbacks: "omniauth_callbacks", :sessions => "sessions" }

I would like to customise this, so I'm trying to test it using RSpec. The question is how do I test this method and the redirects?

If in the spec I put user_omniauth_callback_path(:facebook) it doesn't complain about the route not existing, but doesn't seem to actually call the method.

According to this answer "controller tests use the four HTTP verbs (GET, POST, PUT, DELETE), regardless of whether your controller is RESTful." I tried get user_... etc. but here it does complain that the route doesn't exist. And indeed if I do rake routes it shows there is no HTTP verb for this route:

user_omniauth_callback [BLANK] /users/auth/:action/callback(.:format) omniauth_callbacks#(?-mix:facebook)

Can you see what I'm missing?

EDIT

So following this question one way of calling the method is:

controller.send(:all)

However I then run into the same error that the questioner ran into:

ActionController::RackDelegation#content_type delegated to @_response.content_type, but @_response is nil
Community
  • 1
  • 1
Derek Hill
  • 5,965
  • 5
  • 55
  • 74

3 Answers3

5

You will need to do three things to get this accomplished.

  • enter OmniAuth test environment
  • create an OmniAuth test mock
  • stub out your from_omniauth method to return a user

Here is a possible solution, entered in the spec itself (spec/feature/login_spec.rb for example) . . .

let(:current_user) { FactoryGirl.create(:user) }

before do
  OmniAuth.config.test_mode = true
  OmniAuth.config.mock_auth[:facebook] = OmniAuth::AuthHash.new({
    provider: :facebook,
    uid:'12345',
    info: {
      name: "Joe"
    }
  })
  User.stub(:from_omniauth).and_return(current_user)
end

I adapted this from a google authentication, so facebook may require more fields, but those are the only ones required by omniauth docs. You should be able to find the correct fields by looking at your database schema and finding fields that match the documentation.

In my case, the minimum was enough to pass the request phase and move onto the stubbed out method returning my user.

This example also uses FactoryGirl.

It may not be perfect, but I hope it helps. Good luck!

-Dan

Dan Williams
  • 3,769
  • 1
  • 18
  • 26
  • Perfect. Helpful on many levels, not least that I was thinking about this as a controller spec rather than a feature spec. Much appreciated! – Derek Hill May 16 '13 at 13:00
  • 1
    Glad to help! This brought me to my knees for a day and a half. I'm glad someone else can benefit from my frustration! Was the syntax far off? If so, please edit with the correct solution. Thanks! – Dan Williams May 16 '13 at 13:53
  • 1
    The syntax worked perfectly. The only change I made was pulling a bit into spec_helper.rb following [this answer](http://stackoverflow.com/a/9915796/1450420) which I found thanks to your answer. – Derek Hill May 16 '13 at 14:01
  • Ahhh, interesting. I stubbed out the method so I wouldn't need an actual result saved, but that is probably a more accurate representation. I'm assuming by setting the env.request["omniauth.env"], you can grab a user from persistance and have no need for the stubbing, right? Also, moving the omniauth test setup into a spec_helper is definitely more dry. Thanks for the update! – Dan Williams May 17 '13 at 09:30
  • The only change I made was pulling a bit more into spec_helper.rb. I didn't try setting the `request.env["omniauth.auth"]` in a before block. I was happy enough that it was dry and working so I thought it best to quit while I was ahead! – Derek Hill May 17 '13 at 12:38
  • I haven't touched it in a few years, so sadly, no not adjusted. – Dan Williams May 25 '16 at 13:43
3

If you hit this and you are running rspec 3.4 this example should work for you:

describe Users::OmniauthCallbacksController, type: :controller do
  let(:current_user) { FactoryGirl.create(:user) }

  before do
    OmniAuth.config.test_mode = true
    OmniAuth.config.mock_auth[:your_oauth_provider_here] = OmniAuth::AuthHash.new(
      provider: :your_oauth_provider_here,
      uid: rand(5**10),
      credentials: { token: ENV['CLIENT_ID'], secret: ENV['CLIENT_SECRET'] }
    )
    request.env['devise.mapping'] = Devise.mappings[:user]
    allow(@controller).to receive(:env) { { 'omniauth.auth' => OmniAuth.config.mock_auth[:your_oauth_provider_here] } }
    allow(User).to receive(:from_omniauth) { current_user }
  end

  describe '#your_oauth_provider_here' do
    context 'new user' do
      before { get :your_oauth_provider_here }

      it 'authenticate user' do
        expect(warden.authenticated?(:user)).to be_truthy
      end

      it 'set current_user' do
        expect(current_user).not_to be_nil
      end

      it 'redirect to root_path' do
        expect(response).to redirect_to(root_path)
      end
    end
  end
end
Chris Hough
  • 3,389
  • 3
  • 41
  • 80
1

I am experiencing problem for writhing RSpec for OmniauthCallbacksController, do some research on this and it working for me. Here is my codes, if anyone found necessary. Tests are for happy path and it should work for news version of RSpec eg. 3.x

   require 'spec_helper'

    describe OmniauthCallbacksController, type: :controller do
      describe "#linkedin" do
        let(:current_user) { Fabricate(:user) }

        before(:each) do
          OmniAuth.config.test_mode = true
          OmniAuth.config.mock_auth[:linkedin] = OmniAuth::AuthHash.new({provider: :linkedin, uid: '12345', credentials: {token: 'linkedin-token', secret: 'linkedin-secret'}})
          request.env["devise.mapping"] = Devise.mappings[:user]

          @controller.stub!(:env).and_return({"omniauth.auth" => OmniAuth.config.mock_auth[:linkedin]})
          User.stub(:from_auth).and_return(current_user)
        end

        describe "#linkedin" do
          context "with a new linkedin user" do
            before { get :linkedin }

            it "authenticate user" do
              expect(warden.authenticated?(:user)).to be_truthy
            end

            it "set current_user" do
              expect(subject.current_user).not_to be_nil
            end

            it "redirect to root_path" do
              expect(response).to redirect_to(root_path)
            end
          end
        end

      end
    end
Rokibul Hasan
  • 4,078
  • 2
  • 19
  • 30