Skip to content

Instantly share code, notes, and snippets.

@bhalash
Last active January 17, 2019 07:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save bhalash/8ad2a9dfc0cb05dc52c4 to your computer and use it in GitHub Desktop.
Save bhalash/8ad2a9dfc0cb05dc52c4 to your computer and use it in GitHub Desktop.
Capybara, RSpec and AuthLogic

RSpec, Capybara and AuthLogic

This gist is dedicated to all of my fellow clueless n00bs who are frightened by the combination of RSpec, Capybara and Authlogic.

This gist will not tell you how to install Ruby, Rails, RSpec, Capybara or Authlogic. For the most part, you can install any of these by running either:

brew install <packagename>
bundle install <packagename>

At the point in time that you reach this gist, you should have all of them installed.

0. Add AuthLogic

These steps are derived from a RubyDoc Article. AuthLogic requires a controller to be called against.

0a. spec/rails_helper.rb

require 'spec_helper'
require 'rspec/rails'
require 'factory_girl'

ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  # Load Capybara
  require 'capybara/rspec'

  # Load AuthLogic
  require 'authlogic/test_case'
  config.include Authlogic::TestCase

  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
end

The spec tests require several modules (like factory_girl and of course AuthLogic). The important lines here are the two which load AuthLogic. There are other places where you can load the library, but this worked for me.

0b. spec/features/my_feature_test.rb

Add these includes to the top of the tile:

require 'spec_helper'
require 'rails_helper'

rails_helper includes AuthLogic, which becomes available here. Next, before your block of tests you must activate AuthLogic:

it 'While authenticated do' do
  setup :activate_authlogic 
  # Tests with authentication go here.
end

AuthLogic should work at this point. It won't do anything, but it should work.

1. Make AuthLogic Do Stuff

This took me too long to figure out because

  1. most AuthLogic test documentation was written with Test::Unit in mind;
  2. test specs used Cucumber instead of RSpec;
  3. peak use of AuthLogic was about five years ago-tutorials lag a few versions behind the latest Rails;
  4. and some tutorials are plain bad.

1a. UserSession.create(user) Won't Work

"WTF?!" you wonder. I mean, all the tutorials tell you to do it like that. This is a good enough method for a controller or model tels, but for a feature test, for an AuthLogic session to work, there must exist:

  1. A user record in the database. Not in memory, or anywhere else.
    • User.create(foobar) will add a user to the database.
    • FactoryGirl.create(:user) will add a user to the database.
    • FactoryGirl.build(:user) will not add a user to the database.
  2. A session cookie in the browser.
    • UserSession.create(user) creates a session on the server. It does not add a cookie to the browser.
    • When Capybara loads the page, AuthLogic checks the browser for a cookie.
    • Since this cookie is not present, authentication fails and the fallback action is executed.
    • There are two ways to create a cookie:
      1. Fill out the login form.
      2. Hand-generate a cookie after UserSession.create(user).

1b. yield

Because RSpec tests are feature tests of the entire site, it is considered a Bad Idea to skip the login form. You can create cookies, but shouldn't. Instead you should write a method which fills out the login form for you, and then call your test as a yielded block:

def as_user(user)
    log_in_as user
    yield
    log_out
end

...

it 'should have a pony banner' do
  as_user(pony_lover) do
    visit '/ponies'
    expect(page).to have_content 'WE ALL LOVE MAGIC PONIES'
  end
end
FactoryGirl.define do
factory :user do
password = 'squidloverhothot'
id 1
login 'squid'
email 'squidlover@example.com'
nicename 'Squid Lover'
password password
password_confirmation password
# Included so I can explain why these aren't used: AuthLogic creates these
# fields itself when I spawn a user. If I attempt to create these myself
# AuthLogic considers the user to be invalid.
# salt = Authlogic::Random.hex_token
# password_salt salt
# persistence_token Authlogic::Random.hex_token
# crypted_password Authlogic::CryptoProviders::Sha512.encrypt(password_str + salt)
end
factory :tweet, class: Tweet do
content 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
end
end
require 'spec_helper'
require 'rails_helper'
describe 'When viewing Nttr the page' do
# RSpec has test syntax. Capybara only extends this to web page features.
# Cheat sheet for Capybara expect and general syntax:
# Link: https://gist.github.com/zhengjia/428105
# General RSpec expect:
# Link: https://github.com/rspec/rspec-expectations
before(:each) do
visit root_url
end
it 'should display the title' do
# within '#welcome' do
within 'div[id^=welcome]:not(:nth-child(3))' do
# 'within' accepts any valid CSS3 selector, so crazy selectors like the
# above will work grand (but don't worry about anything but general class
# or ID selectors unless you're a crazy-eyed CSS guru like me).
#
# See: http://www.w3schools.com/cssref/css_selectors.asp
expect(page).to have_content 'Welcome to nttr.'
end
end
it do
# If the label is blank, one is generated. This outputs:
#
# should have css ".container"
#
expect(page).to have_css('.container', 'margin-right: auto')
end
# Will be flagged as PENDING: Not yet implemented
it 'and find the registration form'
it 'should display magical ponies' do
within('#register') do
# Deliberate failing test.
expect(page).to have_content 'LITERALLY MAGIC PONIES'
end
end
end
context 'While logged in' do
# Think of it in terms of plain language: You /describe/ a set of tests, you
# /give context/ for a set of tests.
#
# Setup can only be called from the top of a context or describe block.
# See: spec/rails_helper.rb for how AuthLogic was initialized.
setup :activate_authlogic
let(:test_user) do
# See: https://goo.gl/9UzXG6
#
# The difference between before() and let() is that before() is called once,
# either before each or all, before a test. In the 'open the front page'
# block above, the URL is visited once for each test.
#
# In many ways it is similar to before_action or before_filter in a
# controller.
#
# /let/, however, is called once and cached.
#
# Build (used above) does not persist in memory, but create does. It also
# seems to call on Authlogic methods, such that the attributes needed for a
# user session aren't created.
#
# Authlogic uses the user's ID to generate a session. I need to build a
# phantom user who has an ID attribute set.
#
# Ways to to create a user (with FactoryGirl):
#
# 1. Create adds a user to the database.
FactoryGirl.create(:user)
#
# 2. Build creates a user only in memory.
# FactoryGirl.build(:user)
#
# 3. Call the model directly User.create(...).
# spawn_test_user
#
# AuthLogic is picky. In order to create a session, there must exist:
#
# 1. A user in the database. NOT in memory. FactoryGirl.create or
# User.create must be called.
# 2. A session created using the login form (more below).
#
# On AuthLogic feature test sessions:
# UserSession.create(test_user) does nothing at all during a feature test.
# AuthLogic requires a cookie to be present in the browser. So, say you
# create a user session with UserSession.create(test_user). This happens:
#
# 1. A user session is created on the server.
# 2. The client visitor (Capybara, in this case) wants to view authenticated
# areas of the website.
# 3. AuthLogic looks for a cookie named (user_credentials) with the relevant
# session token.
# 4. This cookie does not exist, so the attempt to access the authenticated
# are a fails. AuthLogic then performs whatever action you have set up to
# handle unauthenticated access (redirect to / in my case).
#
# You can work around this, although it goes against the spirit of a
# complete feature test, if you set a cookie after you create the session:
#
# UserSession.create(test_user)
# Capybara.current_session.driver.browser.set_cookie "#{test_user.persistence_token}::#{test_user.send(test_user.class.primary_key)}"
#
# See: http://www.spacevatican.org/2011/12/5/request-specs-and-authlogic/
end
it 'test user should exist' do
expect(User.where(login: test_user.login)).to exist
end
context 'Test user should' do
let!(:session) do
# You could create a session and cookie here if you so wished.
log_in_as test_user
end
it 'see the timeline' do
expect(page).to have_content 'Nttrs'
end
it 'see the broadcast form' do
expect(page).to have_css '#broadcast'
end
end
end
context 'While on the home page as a user' do
setup :activate_authlogic
let(:test_user) do
FactoryGirl.create(:user)
end
it 'should be able to broadcast a nttr' do
as_user test_user do
# Example of yielded block. as_user:
#
# 1. Visits the login page.
# 2. Completes the form with the test user's login and password.
# 3. Submits the login form.
# 4. Yields to the passed block.
within '#broadcast' do
fill_in 'tweet_content', with: 'Lorem ipsum doo doo doo poo poo.'
click_button 'tweet_submit'
end
expect(page.status_code).to be(200)
end
end
end
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
require 'factory_girl'
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Load Capybara
require 'capybara/rspec'
# Load AuthLogic
require 'authlogic/test_case'
config.include Authlogic::TestCase
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
end
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
def spawn_test_user(name = 'birdman')
# This method is not used, but is retained for illustration of this method to
# create dummy users.
password = "ilove#{name.delete(' ').split(//).shuffle!.join}"
salt = Authlogic::Random.hex_token
User.create!(
id: 1,
login: name,
email: "enlightened.#{name}@example.com",
nicename: "Enlightened #{name.capitalize}",
password: password,
password_confirmation: password,
# Included so I can explain why these aren't used: AuthLogic creates these
# fields itself when I spawn a user. If I attempt to create these myself
# AuthLogic considers the user to be invalid.
# password_salt: salt,
# crypted_password: Authlogic::CryptoProviders::Sha512.encrypt(password + salt),
# persistence_token: Authlogic::Random.hex_token
)
end
def log_in_as(user)
visit new_user_session_path
within '#login' do
fill_in 'user_session_login', with: user.email
fill_in 'user_session_password', with: user.password
click_button 'user_session_submit'
end
end
def log_out
visit '/logout'
end
def as_user(user)
log_in_as user
yield
log_out
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment