Search code examples
ruby-on-railsrubyrspecomniauthomniauth-google-oauth2

undefined method `provider' for nil:NilClass, RSpec, OmniAuth


I'm trying to test an authenticated controller with RSpec and OmniAuth. I've followed the integration testing guide on their wiki. When I run the test, I get the following error:

Failure/Error:
       where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
        user.provider = auth.provider
        user.uid = auth.uid
        user.first_name = auth.info.first_name
        user.last_name = auth.info.last_name
        user.email = auth.info.email
        user.picture = auth.info.image
        user.save!
       end

     NoMethodError:
       undefined method `provider' for nil:NilClass

All relevant code is provided in this gist. My hunch is that the mock auth hash isn't being set somehow but I have no way of verifying that. I configure OmniAuth in config/environments/test.rb as shown in the Gist, and I'm pretty sure that file is ran when the application boots.


Solution

  • There are a couple issues that I see. For one, you're not testing the action of signing-in. You're hitting a controller action with oauth data in the request and expecting it to pass authentication. Oauth data isn't like an API key and won't let you auto sign-in like that. You have to hit the specific signin action provided by omniauth which then sets-up your user's session. This should be tested on its own to confirm that your whole oauth sign-in strategy works as expected. If you're testing controller actions that are not directly related to the act of oauth signins, then you should be using the devise test helpers to sign-in users before running tests that would require authentication.

    Also, you don't want to be setting the OmniAuth configuration in an environment initialiser. The documentation suggests, and what I do myself, is set the configuration down in the tests. For one thing, this allows you to test different kinds of scenarios. For example, this is how I test that my omniauth callback controllers are working and doing what I want:

    context 'with valid google credentials' do
      # this should actually be created in a factory
      let(:provider) { :google_oauth2 }
      let(:oauth) { OmniAuth::AuthHash.new provider: provider, uid: '1234' }
      before do
        OmniAuth.config.test_mode = true
        OmniAuth.config.mock_auth[provider] = oauth
      end
    
      it 'creates a new user' do
        expect { visit "/users/auth/#{provider}" }.to change(User, :count).by(1)
      end
    end