Rails 5 Function testing (Rspec/Devise conflict?) NoMethodError: undefined method admin? for #<User

Rails 5 Function testing (Rspec/Devise conflict?) NoMethodError: undefined method admin? for #<User

我正在学习 Rails 课程,尝试对我的应用程序进行功能测试。我 运行 遇到了一个问题,该问题似乎表明 Rspec 没有使用它与 Devise (CanCanCan) 一起工作所需的助手。

Link 到下面的 repo。我是一个相当新的开发人员,但努力使其可读。谢谢,感谢所有反馈。

问题来自我的 users_controller_spec.rb 文件:

Failures:

  1) UsersController GET #show User is logged in loads correct user details
 Failure/Error: if user&.admin?

 NoMethodError:
   undefined method `admin?' for #<User:0x00000005c08eb0>
 # ./app/models/ability.rb:14:in `initialize'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/cancancan-1.15.0/lib/cancan/controller_additions.rb:361:in `new'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/cancancan-1.15.0/lib/cancan/controller_additions.rb:361:in `current_ability'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/cancancan-1.15.0/lib/cancan/controller_additions.rb:342:in `authorize!'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/cancancan-1.15.0/lib/cancan/controller_resource.rb:49:in `authorize_resource'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/cancancan-1.15.0/lib/cancan/controller_resource.rb:34:in `load_and_authorize_resource'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/cancancan-1.15.0/lib/cancan/controller_resource.rb:10:in `block in add_before_action'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/rails-controller-testing-1.0.1/lib/rails/controller/testing/template_assertions.rb:61:in `process'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:33:in `block in process'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:100:in `catch'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:100:in `_catch_warden'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:33:in `process'
 # /home/scorpian55/.rvm/gems/ruby-2.3.1@unit4-1/gems/rails-controller-testing-1.0.1/lib/rails/controller/testing/integration.rb:12:in `block (2 levels) in <module:Integration>'
 # ./spec/controllers/users_controller_spec.rb:15:in `block (4 levels) in <top (required)>'

Finished in 0.36791 seconds (files took 4.43 seconds to load)
7 examples, 1 failure

Failed examples:

rspec ./spec/controllers/users_controller_spec.rb:14 # UsersController GET #show User is logged in loads correct user details

在研究中,我看到的大多数错误都与 nil:NilClass 的 NoMethodError 相关,但我没有遇到同样的错误,那是一个不同的问题,而不是完全相同的错误消息。

我检查过:

1) spec/rails_helper.rb 有:

config.include Devise::Test::ControllerHelpers, :type => :controller

2) spec/spec_helper.rb 有:

require 'spec_helper'
require 'rspec/rails'
# note: require 'devise' after require 'rspec/rails'
require 'devise'

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, :type => :controller
end

每:https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-(and-RSpec)#controller-specs

以下是相关文件(和我的回购:https://github.com/ScorpIan555/gitwork):

users_controller_spec.rb

 require 'rails_helper'

describe UsersController, :type => :controller do

let(:user) { User.create!(email: "user#{rand(100000).to_s}@examples.com", password: '1234567890') }

  describe 'GET #show' do

 context 'User is logged in' do
  before do
    sign_in user
  end

  it 'loads correct user details' do
    get :show, id: user.id
    expect(response).to have_http_status(200)
    expect(assigns(:user)).to eq user
  end

end #end of first context

 context 'No user is logged in' do
   it 'redirects to login' do
     get :show, id: user.id
     expect(response).to redirect_to(new_user_session_path)
   end
 end

  end #end of GET#show block

end #end of whole block

ability.rb:

class Ability
  include CanCan::Ability

  def initialize(user)

# Define abilities for the passed in user here. For example:
#
 user ||= User.new # guest user (not logged in)

 alias_action :create, :read, :update, :destroy, to: :crud

 can :manage, User, id: user.id

  if user&.admin?

    can :crud, Product
    can :crud, User
    can :crud, Comment

   elsif user&.signed_in?

    can :read, Comment
    can :create, Comment
    can :read, Product
    #can :invite, :User

  else
    can :read, Comment
    can :read, Product

   end #end if/else
  end #end def initialize(user)
end  #end class Ability

users_controller.rb:

  class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!
  load_and_authorize_resource

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end

  #  POST /users
  # POST /users.json
  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was     successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
    format.json { render json: @user.errors, status: :unprocessable_entity }
  end
end
  end

  # PATCH/PUT /users/1
  # PATCH/PUT /users/1.json
  def update
respond_to do |format|
  if @user.update(user_params)
    format.html { redirect_to @user, notice: 'User was successfully updated.' }
    format.json { render :show, status: :ok, location: @user }
  else
    format.html { render :edit }
    format.json { render json: @user.errors, status: :unprocessable_entity }
  end
end
  end

  # DELETE /users/1
  # DELETE /users/1.json
  def destroy
    @user.destroy
    respond_to do |format|
      format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
def set_user
  @user = User.find(params[:id])
end

# Never trust parameters from the scary internet, only allow the white list through.
def user_params
  params.require(:user).permit(:first_name, :last_name)
end
end

schema.db

ActiveRecord::Schema.define(version: 20161219012011) do

  create_table "comments", force: :cascade do |t|
t.integer  "user_id"
t.text     "body"
t.integer  "rating"
t.integer  "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_comments_on_product_id"
t.index ["user_id"], name: "index_comments_on_user_id"
  end

  create_table "orders", force: :cascade do |t|
t.integer "user_id"
t.integer "product_id"
t.float   "total"
t.index ["product_id"], name: "index_orders_on_product_id"
t.index ["user_id"], name: "index_orders_on_user_id"
  end

  create_table "products", force: :cascade do |t|
t.string   "name"
t.text     "description"
t.string   "image_url"
t.datetime "created_at",  null: false
t.datetime "updated_at",  null: false
t.string   "color"
t.decimal  "price"
  end

  create_table "users", force: :cascade do |t|
t.string   "first_name"
t.string   "last_name"
t.datetime "created_at",                          null: false
t.datetime "updated_at",                          null: false
t.string   "email",                  default: "", null: false
t.string   "encrypted_password",     default: "", null: false
t.string   "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer  "sign_in_count",          default: 0,  null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string   "current_sign_in_ip"
t.string   "last_sign_in_ip"
t.boolean  "admin",                  default: false, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_many :orders
  has_many :comments  #added troubleshooting 6.3, need to test

end

你的代码没有明显的错误,所以我检索了你的 repo,在安装 gems 和 运行 之后 db:setup 我发现我复制了你的错误。

我不确定您在问题中发布的架构来自何处,但它不是您的数据库正在使用的架构。回购中的那个没有管理布尔值:

  create_table "users", force: :cascade do |t|
    t.string   "first_name"
    t.string   "last_name"
    t.datetime "created_at",                             null: false
    t.datetime "updated_at",                             null: false
    t.string   "email",                  default: "",    null: false
    t.string   "encrypted_password",     default: "",    null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,     null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

我确实注意到您确实有一个迁移来添加该列。您的迁移和架构以某种方式不同步(可能您在 运行 迁移后恢复到以前的架构)。最好的办法是重新创建添加该列的迁移:

rails g migration add_admin_flag_to_users --force

编辑迁移并添加以下内容:

add_column :users, :admin, :boolean, default: false, null: false

运行 rails db:migraterails db:migrate RAILS_ENV=test

以下现在有效:

$ rails c
Running via Spring preloader in process 60107
Loading development environment (Rails 5.0.0.1)
2.3.1 :001 > user = User.new
 => #<User id: nil, first_name: nil, last_name: nil, created_at: nil, updated_at: nil, email: "", admin: false>
2.3.1 :002 > user.admin?
 => false
2.3.1 :003 >

注意你的测试在验证时仍然会失败,但这是一个完全独立的问题,你现在可以调查这个问题,因为你已经通过了这个障碍:)

顺便说一句,在您的研究中遇到了 nil 类 的 NoMethodError,您的代码受到保护:

user&.admin?

& 字符基本上是说如果用户不为零则只尝试此操作,相当于:

user.admin? if user