views:

29

answers:

2

I'm working on a Rails application that requires you to be logged in to see pretty much anything. For unit tests, this isn't a problem since they interact with the model directly. For integration tests, this isn't a big problem since I can just have the appropriate user log in via Rails' post method. It seems like it makes sense to put all rights-sensitive tests in the integration tests (e.g., test that a user cannot edit someone else's comments).

However, I run into issues when handling the functional tests. It seems like it makes sense for me to test functionality that does not hinge crucially on user status (other than in its ability act as a gateway) independently of user rights. Now, I can't figure out a good way to do this, since all requests bounce the test client to the login page.

An inelegant solution I tried would be to put this log in request in the setup method, but the Rails tester tried to access an action, instead of a path. In particular, I had

def setup
  post /sessions/create, {:username => 'testdbadmin', :password => 'password}
end

But according to the logs, it just tried to access SomeController#/sessions/create, which is clearly not the intention.

I feel like I'm missing some big, built-in way to do this. Please advise.

+1  A: 

I do this in my functional tests by just setting valid logged-in session data in my setup, effectively simulating a request where a user has already authenticated (or hasn't, as the case may be).

I have a helper method in my test_helper.rb:

def login(typ = :user, options = {})
    user = Factory.create(typ, {:username => "Joe"}.merge(options))
    session["user_id"] = user.id.to_s
  end

And then in my functional test:

class AccountsControllerTest < ActionController::TestCase
  context "Fresh session" do
    setup do
      @user = Factory.create(:user)
    end

    should "be redirected to the login page if not logged in" do
      get :show
      assert_redirected_to login_path
    end

  context "Logged in user with profile" do
    setup do
      @user = login
    end

    should "get the account home page" do
      get :show
      assert_response :success
    end
  end
end
Chris Heald
+1  A: 

Rather than test all of this in a functional test, I would use functional tests and Cucumber features in combination. You're wanting to test that when the user logs in, that they then can access various parts of the application. This is the Cucumber feature. Then you're wanting to test that when the user isn't logged in that they can't access these parts. That's the functional test.

I would write the Cucumber feature like this:

Feature: Viewing accounts
  In order to update my account information
  As a user
  I want to access my profile page

  Background:
    Given a user exists:
      | login | password      |
      | Radar | superpassword |
    And I am logged in as them

  Scenario: My profile page
    When I follow "Account"
    Then I should see "Profile Information"

I assume a couple of things here. Firstly your users login using a login and a password. You may have this as an email and password, but I'm sure you understand the gist.

The "I am logged in as them" step doesn't actually set up the session but rather actually goes through the process of logging in the user. I would write these two Background steps like this*:

Given /^a user exists:$/ do |table|
  table.hashes.each do |user|
    @user = User.create!(user)
  end
end


Given /^I am logged in as them$/ do 
  steps(%Q{
    Given I am on the homepage
    And I follow "Login"
    And I fill in "Login" with "#{@user.login}"
    And I fill in "Password" with "#{@user.password}"
    And I press "Login"
    Then I should see "Login successful."
  })
end

With these steps defined then the Scenario will run. I assume here that you have a link somewhere on the page where the login action redirects to that says "Account". When this link's clicked, I also assume that it goes to a page that says "Profile Information".

* It's a slightly contrived example. The "a user exists" step can take multiple rows.


Now for the functional test. I'm an RSpec sort of guy, so I'd write it as this.

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe AccountsController do

  context "not logged in users" do
    it "cannot access the profile action" do
      get :profile
      flash[:alert].should eql("You must be logged in to access that action.")
      response.should redirect_to(login_path)
    end
  end
end

The before_filter that checks that the user is logged in will be tested by this functional test. Because it's a private method (it's a private method, right?) it should not be tested directly. Here again I make a couple of assumptions like that when this before_filter "fails" it sets a flash[:alert] to some particular message and redirects to the login_path.


So in summary: Test all the things the user can do by clicking around and filling in forms on the site using Cucumber, but for things such as going to protected actions or missing resources use a functional test / controller spec.

Ryan Bigg
Thanks for your thorough answer and examples. I've familiarized myself with the gist of Cucumber, although the time constraints as such that I can't apply it to this immediate project, I will definitely give this level of abstract BDD in the future. I see the reason behind your assessment that I should not be testing rights managed functionality in functional tests and should put that in integration/cucumber tests instead.
Steven Xu