Rails 看《Rails Tutorial》,添加禁止登录用户再次访问登陆页面时,测试用例报错。怎么破?

diguage · 2013年07月10日 · 最后由 yangman_wenzhu 回复于 2015年10月14日 · 3830 次阅读

本人在看《Ruby on Rails Tutorial(第二版)》学习 Rails。看完第九章修改用户信息。在做练习六时,遇到了一些诡异问题。陈述如下:

练习六( http://railstutorial-china.org/chapter9.html#sec-9-6 )题目如下: 已经登录的用户就没必要再访问 Users 控制器的 new 和 create 动作了,修改程序,如果登录后的用户访问这些地址时,转向到网站首页。

这个练习功能正常实现,实现方式如下(以下代码的第一行为文件路径):

#/app/helpers/sessions_helper.rb

module SessionsHelper
  def signed_in?
    !current_user.nil?      
  end

  def current_user
    @current_user ||= User.find_by_remember_token(cookies[:remember_token])    
  end

  def forbid_signed_user  # 这是我添加的跳转辅助方法
    if signed_in?
      redirect_to root_path
      return
    end
  end
end

UserController中的实现如下:

# /app/controllers/users_controller.rb

class UsersController < ApplicationController
  # …

  before_filter :forbid_signed_user, only:[:new, :create]

  def create
    @user = User.new(params[:user])    

    if @user.save
      sign_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  # …
end

到这里一切正常。再我们接着往下看。

我想 既然是登陆用户,也没必要访问登陆页面了。所以,我想针对登陆页面也添加如此限制。具体实现如下:

# /app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  before_filter :forbid_signed_user, only:[:new, :create]  # 使用filter来实现

  def new
  end

  def create
    user = User.find_by_email(params[:session][:email])     
    if user && user.authenticate(params[:session][:password])
      sign_in user
      redirect_back_or user
    else
      flash.now[:error] = 'Invalid email/password combination' # Not quite right!
      render 'new'
    end
  end

  # …
end

然后,问题就来了。测试用例的报错信息如下:

Failures:

  1) UserPages index delete links as an admin user  
     Failure/Error: sign_in admin
     Capybara::ElementNotFound:
       cannot fill in, no text field, text area or password field with id, name, or label 'Email' found
     # (eval):2:in `fill_in'
     # ./spec/support/utilities.rb:5:in `sign_in'
     # ./spec/requests/user_pages_spec.rb:36:in `block (5 levels) in <top (required)>'

  2) UserPages index delete links as an admin user  should be able to delete another user
     Failure/Error: sign_in admin
     Capybara::ElementNotFound:
       cannot fill in, no text field, text area or password field with id, name, or label 'Email' found
     # (eval):2:in `fill_in'
     # ./spec/support/utilities.rb:5:in `sign_in'
     # ./spec/requests/user_pages_spec.rb:36:in `block (5 levels) in <top (required)>'

  3) UserPages index delete links as an admin user  
     Failure/Error: sign_in admin
     Capybara::ElementNotFound:
       cannot fill in, no text field, text area or password field with id, name, or label 'Email' found
     # (eval):2:in `fill_in'
     # ./spec/support/utilities.rb:5:in `sign_in'
     # ./spec/requests/user_pages_spec.rb:36:in `block (5 levels) in <top (required)>'

Finished in 13.29 seconds
83 examples, 3 failures

Failed examples:

rspec ./spec/requests/user_pages_spec.rb:40 # UserPages index delete links as an admin user  
rspec ./spec/requests/user_pages_spec.rb:41 # UserPages index delete links as an admin user  should be able to delete another user
rspec ./spec/requests/user_pages_spec.rb:44 # UserPages index delete links as an admin user  

错入信息中提到的两个文件如下: utilities.rb代码如下:

# /spec/support/utilities.rb

include ApplicationHelper

def sign_in(user)
  visit signin_path
  fill_in "Email", with: user.email 
  fill_in "Password", with: user.password 
  click_button "Sign in"
  # Sign in when not using Capybars as well
  cookies[:remember_token] = user.remember_token
end

user_pages_spec.rb中部分代码如下:

# /spec/requests/user_pages_spec.rb

require 'spec_helper'

describe "UserPages" do

  subject { page }

  describe "index" do
    let(:user) { FactoryGirl.create(:user) }
    before(:each) do 
      sign_in user
      visit users_path
    end

    it { should have_selector('title', text:'All users') }
    it { should have_selector('h1', text: 'All users') }

    describe "pagination" do
      before(:all) { 80.times { FactoryGirl.create(:user) } }
      after(:all) { User.delete_all }

      it { should have_selector('div.pagination') }

      it "should list each user" do 
        User.paginate(page: 1).each do |user|
          page.should have_selector('li', text:user.name)        
        end
      end
    end

    describe "delete links" do 
      it { should_not have_link('delete') }

      describe "as an admin user " do 
        let(:admin) { FactoryGirl.create(:admin) }
        before do 
          sign_in admin
          visit users_path
        end

        it { should have_link('delete', href: user_path(User.first)) }
        it "should be able to delete another user" do 
          expect { click_button('delete').to change(User, :count).by(-1) }
        end
        it { should_not have_link('delete', href: user_path(admin)) }
      end
    end
  end
end

疑问:为啥会有这些问题? 是不是因为在如下代码:

describe "as an admin user " do 
  let(:admin) { FactoryGirl.create(:admin) }
  before do 
    sign_in admin
    visit users_path
  end

之前已经登录过来?登陆代码 (就是user_pages_spec.rb开头部分的代码) 如下:

describe "index" do
  let(:user) { FactoryGirl.create(:user) }
  before(:each) do 
    sign_in user
    visit users_path
  end

再次疑问:每一个describe "tips" do…end块(暂且成为子describe块)和上层的describe "tips" do…end块(暂且成为父describe块)之间是什么关系?父describe块的操作会对子describe块产生什么影响?

代码: https://github.com/diguage/rortutorial 说明:我已经将第九章的代码提交到了develop分支,还没有将第九章的代码合并到master分支上。下载测试代码时,请注意。谢谢!

《Ruby on Rails Tutorial(第二版)》中文版: http://railstutorial-china.org/

是不是因为你做的限制后,不会 render 登录的页面了,所以找不到那些页面元素去填入用户名,密码了

#1 楼 @williamherry 我在浏览器里访问是没有问题的,登陆后不能访问;没登陆则可以访问。逻辑是没有错。 我怀疑是在describe "as an admin user" do之前代码中的sign_in user对后面的测试用例有影响。不知这样猜是否正确?!

好像有个 save_and_open 你加上试试看能不能找到问题

#3 楼 @williamherry save_and_open这是什么?没有见过,怎么用啊?

你 google 一下吧,我记不清了,就是执行到这里的时候把状态保存并打开浏览器,应该是直接写在测试代码里

  1. 想知道 你为什么把 remember_token 放在 cookies 中 cookies[:remember_token]
bjtugun rspec 和 capybara 的 cookies 是不同的 提及了此话题。 04月03日 10:57
需要 登录 后方可回复, 如果你还没有账号请 注册新账号