2

我正在阅读 Michael Hartl 的 Rails 教程的 Rails 4 版本,并且遇到了第 9.6 节练习 1(清单 9.49)的问题。

看起来教程中的测试由于错误的原因通过了。在 PATCH 请求之前,user.admin? 默认为假;在 PATCH 请求 user.admin 之后?仍然是错误的(因此通过了测试),因为 PATCH 请求没有到达 UsersController#update 方法。

这是我的代码:

spec/requests/user_pages_spec.rb (other tests removed to isolate the one in question):

require 'spec_helper'
describe "User pages" do
  subject { page }      
  describe 'edit' do
    let(:user) { FactoryGirl.create(:user) }
    before do
      sign_in user
      visit edit_user_path(user)
    end
    describe "forbidden attributes" do
      let(:params) do
        { user: { name: 'Forbidden Attributes',
                  password: user.password,
                  password_confirmation: user.password,
                  admin: true } }
      end
      before { patch user_path(user), params }
      specify { expect(user.reload).not_to be_admin  }
    end    
  end
end

Relevant parts of app/controllers/users_controller.rb:

class UsersController < ApplicationController
  before_action :signed_in_user, only: [:index, :edit, :update]
  before_action :correct_user,   only: [:edit, :update]

  # PATCH /users/:id
  def update
    # @user is set in before_action
    if @user.update_attributes(user_params)
      # handle a successful update
      flash[:success] = 'Profile updated'
      sign_in @user
      redirect_to @user
    else
      render 'edit'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                  :password_confirmation, **:admin**)
    end

    # Before filters

    def signed_in_user
      unless signed_in?
        store_location
        redirect_to signin_url, notice: 'Please sign in.'
      end
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end
end

spec/factories.rb:

FactoryGirl.define do
  factory :user do
    sequence(:name) { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com" }
    password  "foobar"
    password_confirmation "foobar"
    
    factory :admin do
      admin true
    end
  end
end

以下是测试日志显示的情况:

Started PATCH "/users/2111" for 127.0.0.1 at 2013-08-18 21:30:44 -0400
Processing by UsersController#update as HTML
  Parameters: {"user"=>{"name"=>"Forbidden Attributes", "password"=>"[FILTERED]", \
"password_confirmation"=>"[FILTERED]", "admin"=>"true"}, "id"=>"2111"}
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."remember_token" = \
'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
**Redirected to http://www.example.com/signin
Filter chain halted as :signed_in_user rendered or redirected**
Completed 302 Found in 2ms (ActiveRecord: 0.4ms)

我从https://github.com/railstutorial/sample_app_rails_4下载了代码的参考版本并运行了rspec spec/requests/user_pages_spec.rb. 测试日志显示了同样的事情: PATCH 请求被 signed_in_user 停止并且永远不会进入更新方法。

当我测试 admin IS 设置并添加了一些 puts 语句时,看起来正在登录的用户与正在测试的用户不同;user.id 保持不变,但 user.name 会发生变化。我想知道它是否与工厂中的 sequence() 调用有关。

  1. 任何人都可以验证或反驳我的发现吗?
  2. 如何正确编写此测试?

找到解决方案

进一步的调查似乎牵涉remember_token。如果我将“禁止属性”测试移出“编辑”块并将“capybara:true”添加到sign_in调用中,它就可以工作。所以代码清单 9.49 (spec/requests/user_pages_spec.rb) 应该是这样的:

require 'spec_helper'

describe "User pages" do

  subject { page }
  .
  .
  .
  describe "update forbidden attributes" do
    let(:user) { FactoryGirl.create(:user) }
    let(:params) do
      { user: { admin: true, password: user.password,
                password_confirmation: user.password } }
    end
    before do
      sign_in user, no_capybara: true
      patch user_path(user), params 
    end
    specify { expect(user.reload).not_to be_admin }
  end
end
4

2 回答 2

1

我只是想确认当“禁止属性”测试嵌套在“编辑”测试块中时,我看到了相同的行为。

我的笔记表明,在第 9 章中提到,当您执行直接 POST、PATCH、GET 或 DELETE 请求时,而不是使用访问,需要将 no_capybara: true 选项提供给 sign_in 方法以确保用户已登录。

但是,在这种情况下,如果您将 no_capybara: true 选项与 sign_in 一起使用,“编辑”块中的其他测试将由于某些 Capybara 问题而失败。

如 OP 所述,如果省略该选项,则“禁止属性”测试通过,无论用户控制器中 user_params 方法中是否存在 :admin 。

于 2013-08-25T02:11:35.723 回答
0

这里同样的问题。在 metafour 的帮助下,我找到了以下作品。我们需要使用 capybara: true 登录用户才能使补丁工作。

describe "forbidden attributes" do
  let(:params) do
    { user: { admin: true, password: user.password,
              password_confirmation: user.password } }
  end
  before do
    sign_in user, no_capybara: true
    patch user_path(user), params
  end
  specify { expect(user.reload).not_to be_admin }
end
于 2013-08-28T00:16:39.613 回答