Recently I’ve been working on a new project where I’m using omniauth and omniauth-identity instead of devise to manage login and password for users. That’s mainly because I wanted to have a separation between user and authentication data (but that’s for another post).

The upside of this approach is that I have full control over how identity information is stored. The downside is that I have to do everything on my own (which isn’t that hard, and you learn a lot about the inner workings of devise, because you’ll likely end up in their excellent source code anyways to see how they did stuff).

Rails Routing Constraints

Last time I was working on some routing constraints: guest to my site should end up on a general landing page, but logged in users should see their list of projects. With devise it’s handled like so:

Rails.application.routes.draw do
  devise_for :users

  authenticated :user do
    root 'projects#index'
  end

  root 'pages#landing'
end

Looking into the source of devise, you’ll find this:

def authenticated(scope=nil, block=nil)
  constraints_for(:authenticate?, scope, block) do
    yield
  end
end

# ...

def constraints_for(method_to_apply, scope=nil, block=nil)
  constraint = lambda do |request|
    request.env['warden'].send(method_to_apply, scope: scope) &&
      (block.nil? || block.call(request.env["warden"].user(scope)))
  end

  constraints(constraint) do
    yield
  end
end

As you can see (and probably know), devise is using warden to help with authenticating Rack session. constraints_for checks if the warden object returns true when calling the authenticate? method. It also accepts an additional block, which receives the user object, so you can check check for additional conditions, e.g. if the user has admin rights.

I’ve kept things a bit more barebone for now, so I’m only setting a key on the request’s session object for now. I also prefer to create a routing constraint class, as this will make testing a bit easier, in my opinion:

class AuthenticatedConstraint
  def matches?(request)
    request.session[:user_id].present?
  end
end

Testing our Constraint

Of course, I want to make sure that the AuthenticatedConstraint class behaves as intended (even though its logic isn’t really complex), but I consider it good practice to write at least a minimal set of unit tests for my classes.

Here’s the basic setup:

require 'spec_helper'

describe AuthenticatedConstraint do
  describe '#matches' do
    it 'returns true if the session contains the key user_id' do
      request = 
      expect(AuthenticatedConstraint.new.matches?(request)).to eq true
    end
  end
end

So far, so good. Now, how do we end up with a valid request object for our test? I’ve found two ways (and I’m happy about additional ideas):

RSpec Test Double

Test Doubles are objects that stand in for another object during our tests. Looking at the request object that we want to mock, we find that we can access a key in our Rails session with request.session[:user_id]. Let’s construct our request object like so:

request = double('request', session: { user_id: 1 })

If we run our unit test with rspec, it will pass. Great!

Using ActionDispatch::TestRequest

ActionDispatch::TestRequest is one of several testing objects which ship with Rails per default. Let’s initiate it with a rack.session key:

request = ActionDispatch::TestRequest.create('rack.session' => { user_id: 1 })

Running our specs with rspec now will… fail! Up to now, we didn’t use any part of Rails (noticed that our AuthenticatedConstraint is just a PORO?). To make it work, we need to require rails_helper, instead of spec_helper. This will have a negative impact on our setup time for our spec, depending on how big your Rails application is and especially what’s happening during setup time.

It’s up to you if you wanna take that hit or you go with the first version, which doesn’t require any Rails components at all. Maybe with the only downside, that we’re making assumptions about the internal workings of the ActionDispatch::Request object, which we are mocking.