jsz
jsz

Reputation: 1397

capybara/rspec: should GET/PUT tests be in different files?

I'm following the Ruby on Rails Tutorial, and now I need to write tests for the authorization code, e.g. making sure users can only edit their own profile.

There are two actions to test. One is to ensure a user can't access the page of editing other users' profile. This one is easy, a simple "feature" test in capybara.

But I certainly want to test the PUT action too, so that a user can't manually submit a PUT request, bypassing the edit page. From what I read, this should be done as an rspec "request" test.

Now my question is, do I have to maintain them in different dirs? (spec/features vs spec/requests)? It doesn't sound right since these two scenarios are closely related. How are such tests usually done in Rails?

For example,

describe "as wrong user" do
  let(:user) { FactoryGirl.create(:user) }
  let(:wrong_user) { FactoryGirl.create(:user, email: "[email protected]") }
  before { sign_in user }

  describe "visiting Users#edit page" do
    before { visit edit_user_path(wrong_user) }
    it { should_not have_selector('title', text: full_title('Edit user')) }
  end

  describe "submitting a PUT request to the Users#update action" do
    before { put user_path(wrong_user) }
    specify { response.should redirect_to(root_path) }
  end
end

The second test doesn't work in capybara 2.x since "put" is not supported any longer. It has to be a request test. And now I have to write a second "sign_in" method, since the current one uses methods that are only available to feature tests. Smells like a lot of code duplication.

======== my solution ========

After figuring out how to login in a request test, thanks to Paul Fioravanti's answer,

    before do
      post sessions_path, email: user.email, password: user.password
      cookies[:remember_token] = user.remember_token
    end

I changed all tests to request tests. So I don't have to split them into different files. Paul's solution would also work though I think this is cleaner.

describe 'authorization' do
  describe 'as un-signed-in user' do
    let(:user) { FactoryGirl.create(:user) }

    describe 'getting user edit page' do
      before { get edit_user_path(user) }

      specify { response.should redirect_to(signin_path) }
    end

    describe 'putting to user update page' do
      before { put user_path(user) }

      specify { response.should redirect_to(signin_path) }
    end
  end

  describe 'as wrong user' do
    let(:user) { FactoryGirl.create(:user) }
    let(:wrong_user) { FactoryGirl.create(:user, email: '[email protected]') }

    before do
      post sessions_path, email: user.email, password: user.password
      cookies[:remember_token] = user.remember_token
    end

    describe 'getting user edit page' do
      before { get edit_user_path(wrong_user) }

      specify { response.should redirect_to(root_path) }
    end

    describe 'putting to user update page' do
      before { put user_path(wrong_user) }

      specify { response.should redirect_to(root_path) }
    end
  end
end

Upvotes: 3

Views: 1525

Answers (2)

Paul Fioravanti
Paul Fioravanti

Reputation: 16793

I ended up going through the arduous process of splitting up my request and feature specs after I finished The Rails Tutorial and upgraded my Sample App to Capybara 2.0. Since you say you're still currently doing the tutorial, I would advise you to just keep with the gems that Hartl specifies (Capybara 1.1.2), finish your Sample App, and then go back to the requests/features issue as a refactoring exercise. For your reference though, this is how I ended up writing my "wrong user" authorization specs:

spec/support/utilities.rb

def sign_in_through_ui(user)
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign In"
end

def sign_in_request(user)
  post session_path(email: user.email, password: user.password)
  cookies[:remember_token] = user.remember_token
end

RSpec::Matchers::define :have_title do |text|
  match do |page|
    Capybara.string(page.body).has_selector?('title', text: text)
  end
end

spec/features/authentication_pages_spec.rb

describe "Authentication on UI" do

  subject { page }
  # ...
  describe "authorization" do
    # ...
    context "as a wrong user" do
      let(:user)       { FactoryGirl.create(:user) }
      let(:wrong_user) { FactoryGirl.create(:user, email: "[email protected]") }

      before do
        visit root_path
        click_link "Sign In"
        sign_in_through_ui(user)
      end

      context "visiting Users#edit" do
        let(:page_title) { full_title("Edit User") }
        before { visit edit_user_path(wrong_user) }
        it { should_not have_title(page_title) }
      end
    end
  end
end

spec/requests/authentication_requests_spec.rb

describe "Authentication Requests" do

  subject { response }
  # ...
  describe "authorization" do
    # ...
    context "as a wrong user" do
      let(:user)       { FactoryGirl.create(:user) }
      let(:wrong_user) { FactoryGirl.create(:user, email: "[email protected]") }

      before { sign_in_request(user) }

      context "PUT Users#update" do
        before { put user_path(wrong_user) }
        it { should redirect_to(root_url) }
      end
    end

  end
end

I primarily used the following two links as reference when trying to figure out how to separate my feature specs from my request specs:

Update:

If you don't want the custom RSpec matcher, you can also use the following in the tests above to get the same result on the title element:

its(:source) { should have_selector('title', text: page_title) }

Upvotes: 2

Peter de Ridder
Peter de Ridder

Reputation: 2399

According to Jnicklas (https://github.com/jnicklas/capybara) you should move all Capybare specs you have in spec/requests to spec/features, since spec/features will now be used by Capybara 2.x. So this means that once you moved your Capybara specs to features, you could completely remove these specs from the spec/requests directory.

Personally, I've finished the Ruby on Rails tutorial with no problems at all. I used Capybara 2.x and never used spec/features (just the 'old' spec/requests). For Rspec 2.x support you have to add require >'capybara/rspec'< to your spec_helper.rb file. Without it, your tests could fail.

Edit:

I've just read trough the Rspec docs. If you are using Capybara in your specs these specs have to be moved to spec/features. If there is no Capybara involved the specs can simply stay in your requests directory.

Feature specs https://www.relishapp.com/rspec/rspec-rails/v/2-12-2/docs/feature-specs/feature-spec!

Request specs https://www.relishapp.com/rspec/rspec-rails/v/2-12-2/docs/request-specs

More info, from Rubydoc: http://rubydoc.info/github/jnicklas/capybara/master#Using_Capybara_with_RSpec

Upvotes: 1

Related Questions