dmathieu - Authentication in Tests with Warden

Authentication in Tests with Warden

Thursday, 2 August 2012 in Development Articles by Damien Mathieu Creative Commons License

I’m currently working on a personal project, for which I need a simple but effective and reliable json API.
I decided to build this API with Sinatra.
I’m also using warden for user authentication.

So I needed to authenticate my API calls in my tests.
Warden managed it quite easily.

1
2
3
4
5
6
7
8
9
10
11
12
describe MyApi do
  include Warden::Test::Helper
  let(:user) { FactoryGirl.create(:user) }

  before do
    login_as user
  end

  it "should be logged in" do
    get '/'
  end
end

And this call will be authenticated.
This poses a big problem though. Why the frack do I need to create a user in base for this ?
This means I need to load ActiveRecord for user authentication in my controllers.
Warden has its own tests for retrieving the user, and I rely on them to work. I don’t need to test that again in my controller !

So I came up with the following helper :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module MyApp
  module Api
    module Test
      extend ActiveSupport::Concern

      included do
        include Rack::Test::Methods
        let(:current_user) { nil }

        def app
          lambda {|env|
            env['warden'] = warden
            MyApp::Api::App.call(env)
          }
        end

        def warden
          FakeWarden.new current_user
        end
      end

      class FakeWarden
        attr_accessor :user

        def initialize(user)
          @user = user
        end

        def authenticated?
          !!user
        end
      end

    end
  end
end

And I can include it in my tests :

1
2
3
4
5
6
7
8
describe MyApi do
  include MyApp::Api::Test
  let(:current_user) { FactoryGirl.build_stubbed(:user) }

  it "should be logged in" do
    get '/'
  end
end

The first part relies on two things :

  • current_user is the current user. If I don’t wish to be authenticated, I just need to make it nil.
  • MyApp::Api::App is my rack application. Change this class to whatever you want.

By including this in my tests description, I get warden included, Rack::Test too (this is more to make it easy for me), and my app defined.
And that’s all, no other dependencies, no database access. My tests remain fast and I can potentially change my database backend without any impact here.

Don’t forget to test your unauthenticated requests

Just for free, you’ll also find here the matcher I setup with the code above, to make sure all my api requests require authentication.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
RSpec::Matchers.define :be_authenticated do |verb, action|
  match do
    send verb, action
    last_response.status == 401 && last_response.body == {error: 'Unauthorized'}.to_json
  end

  description do
    "authorize resource #{verb}##{action}"
  end

  failure_message_for_should do |text|
    "expected #{verb}##{action} to require authentication - Got status #{last_response.status} with body #{last_response.body}"
  end

  failure_message_for_should_not do |text|
    "expected #{verb}##{action} to not require authentication"
  end
end

Just define your test with the following :

1
it { should be_authenticated :get, '/' }

Of course, you need to make sure current_user is nil for this test. You can specify any valid verb and any url. Enjoy !