全栈工程师成长记

编程重新定义人生

rspec-rails 常见错误提示及解决办法

由于使用 TDD 开发,有些报错是尚未做到某个步骤必然发生的结果,而有些则是本身编写的代码或者测试代码有错。本文将这些常见的错误提示都记录并提供相应的解决办法(有特定内容——比如某个 class 的名称——的位置请自行替换)。

NameError

错误提示:

 Failure/Error: course1 = Course.create(title: "foo", description: "bar")

     NameError:
       uninitialized constant Course

原因及解法:
没有 course 这个 class,因为还没有建立这个 model
运行 rails g model course title:string description:text
运行 rake db:migrate


ActionController::UrlGenerationError

错误提示:

 Failure/Error: get :index

     ActionController::UrlGenerationError:
       No route matches {:action=>"index", :controller=>"courses"}

原因及解法:
尚未建立 routing
修改 routes.rb

routes.rb
  Rails.application.routes.draw do
+    resources :courses
  end

AbstractController::ActionNotFound

错误提示:

Failure/Error: get :index

     AbstractController::ActionNotFound:
       The action 'index' could not be found for CoursesController

原因及解法:
尚未建立 index action
修改 courses_controller.rb

courses_controller.rb
class CoursesController < ApplicationController

+      def index
+        @courses = Course.all
+      end

end

ActionController::UnknownFormat

错误提示:

Failure/Error: get :index

     ActionController::UnknownFormat:
       CoursesController#index is missing a template for this request format and variant.


       request.formats: ["text/html"]
       request.variant: []

原因及解法:
尚未建立 index view
新增 app/views/course/index.html.erb 并填入页面内容即可


NoMethodError

错误提示:

 Failure/Error: expect(assigns[:courses]).to eq([course1,course2])

     NoMethodError:
       assigns has been extracted to a gem. To continue using it,
               add `gem 'rails-controller-testing'` to your Gemfile.

原因及解法:
没有安装 rails-controller-testing 这个gem
修改 gemfile

gemfile
group :development, :test do
  gem 'byebug', platform: :mri
  gem 'rspec-rails'
+      gem 'rails-controller-testing'
end

got: nil

错误提示:

Failure/Error: expect(assigns[:courses]).to eq([course1,course2])
expected: [#<Course id: 1, title: "foo", description: "bar", created_at: "2016-09-20 04:35:06", updated_at: "20...e: "bar", description: "foo", created_at: "2016-09-20 04:35:06", updated_at: "2016-09-20 04:35:06">]

            got: nil

       (compared using ==)

原因及解法:
controller 里面的变量命名错误(手动拼写的坑)
修改 courses_controller.rb

courses_controller.rb
  def index
-    @couses = Course.all
+    @courses = Course.all
  end

spec/support/page_objects/base (LoadError)

错误提示:

rspec spec/features/homepage_spec.rb
spec/support/page_objects/pages/home.rb:1:in `require_relative': cannot load such file
spec/support/page_objects/base (LoadError)

from /Users/nfreeness/Project/classrom/spec/support/page_objects/pages/home.rb:1:in `<top (required)>'

原因及解法:
尚未建立 base.rb
新增 spec/support/page_objects/base.rb

module PageObjects
  class Base
    include Capybara::DSL
    include Rails.application.routes.url_helpers
  end
end

`id` is not available from within an example

错误提示:

 Failure/Error: get :show, id => course.id
       `id` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). 
       It is only available on an example group (e.g. a `describe` or `context` block).

原因及解法:
测试代码拼写错误
修改 spec/support/page_objects/base.rb

-     get :show, id => course.id
+     get :show, :id => course.id

NameError

错误提示:

 Failure/Error: get :show, id => course.id
     Failure/Error: expect(response).to redirect_to coures_path

     NameError:
       undefined local variable or method `coures_path' for #<RSpec::ExampleGroups::CoursesController::GETCreate:0x007f9adf8b0200>

原因及解法:
测试代码拼写错误
修改 spec/controllers/courses_controller_spec.rb

spec/controllers/courses_controller_spec.rb
-    expect(response).to redirect_to coures_path
+    expect(response).to redirect_to courses_path

ActionController::UrlGenerationError

错误提示:

Failure/Error: post :careate, course: {:description => "bar"}

     ActionController::UrlGenerationError:
       No route matches {:action=>"careate", :controller=>"courses", :course=>{:description=>"bar"}}

原因及解法:
controllers/courses_controller.rb 里的 create action 并没错
spec/controllers/courses_controller_spec.rb 里拼写错误
PS:从上面开始纪录了这么多个错误,到这里的时候已经能很快的定位到问题所在了。所以纪录错误真的太重要了。

spec/controllers/courses_controller_spec.rb
-     post :careate, course: {:description => "bar"}
+     post :create, course: {:description => "bar"}


DEPRECATION WARNING

错误提示:

spec/controllers/courses_controller_spec.rb
DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only keyword arguments in future Rails versions.

原因及解法:
这个算不上是错误,而是提示。由于使用了旧版的语法结构,每次运行测试都会出一大堆的版本兼容提示,很是烦人。rials github 上有相关说明

具体的修改是用 params 将 http 动作后面的方法包起来,举例如下:

-  get :show, :id => course.id 
+  get :show, params: { id: course.id }

-  expect { post :create, course: { :description => "bar"}}.to change{Course.count}.by(0)
+  expect { post :create, params: { course: {description: "bar"}}}.to change{Course.count}.by(0)

-  post :create, course: { :description => "bar" }
+  post :create, params: { course: {description: "bar"}}

-  expect{ post :create, :course => FactoryGirl.attributes_for(:course)}.to change{ Course.count}.by(1)
+  expect{ post :create, params: { course: FactoryGirl.attributes_for(:course)}} .to change{ Course.count}.by(1)

-  post :create, :course => FactoryGirl.attributes_for(:course)
+  post :create, params: { course: FactoryGirl.attributes_for(:course)}


ArgumentError

错误提示:

 Failure/Error: @course.update

     ArgumentError:
       wrong number of arguments (given 0, expected 1)

原因及解法:
update action没写对,少了 course_params 导致无法修改文件,所以期待为 1 但输出为 0

controllers/courses_controller.rb
  def update
     @course = Course.find(params[:id])
-    @course.update
+    @course.update(course_params)
     redirect_to course_path(@course)
  end

Devise::MissingWarden

错误提示:

Failure/Error: get :new

     Devise::MissingWarden:
       Devise could not find the `Warden::Proxy` instance on your request environment.
       Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.
       If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object for you.

原因及解法:
在 rspec 的配置中尚未包含 devise 的测试
新增 spec/support/devise.rb ,将 Devise 包含在所有的 controller 测试里。

spec/support/devise.rb
+    RSpec.configure do |config|
+      config.include Devise::TestHelpers, type: :controller
+    end

值得注意的是,对扩展 gem 的支持都是在 spec/support 下面进行配置
比如我们使用 FactoryGirl 来产生假的测试资料,那我们需要新增 spec/support/factory_girl.rb

spec/support/factory_girl.rb
+  RSpec.configure do |config|
+   config.include FactoryGirl::Syntax::Methods
+  end

DEPRECATION WARNING

错误提示:

DEPRECATION WARNING:           [Devise] including `Devise::TestHelpers` is deprecated and will be removed from Devise.
          For controller tests, please include `Devise::Test::ControllerHelpers` instead.
 (called from <top (required)> at /Users/nfreeness/Project/classroom-2/spec/controllers/courses_controller_spec.rb:3)

原因及解法:
Devise 的测试用法不规范
修改 spec/support/devise.rb

spec/support/devise.rb
RSpec.configure do |config|
-      config.include Devise::TestHelpers, type: :controller
+      config.include Devise::Test::ControllerHelpers, type: :controller
end

Trait not registered: email

错误提示:

 Failure/Error: user = FactoryGirl.create(:user)

     ArgumentError:
       Trait not registered: email

原因及解法:
尚未配置 FactoryGirl 来产生 Devise 用户 email
修改 spec/factories.rb

spec/support/devise.rb
  FactoryGirl.define do

+   sequence(:email) { |n| "user#{n}@example.com"}

  end

NoMethodError

错误提示:

Failures:

  1) CoursesController POST create create a course for user
     Failure/Error: expect(Course.last.user).to eq(user)

     NoMethodError:
       undefined method `user' for nil:NilClass

原因及解法:
尚未建立 course 和 user 的从属关系

  1. 为 course 添加 user_id 栏位,add_column :courses, :user_id, :integer
  2. 修改 app/models/course.rb ,增加 belongs_to :user
  3. 修改 app/controller/courses_controller.rb ,指定 course 的 user
app/controller/courses_controller.rb
  def create
    @course = Course.new(course_params)
+   @course.user = current_user

4.修改 spec/modes/course_spec.rb,增加关系验证测试

spec/modes/course_spec.rb
  RSpec.describe Course, type: :model do
    it { is_expected.to validate_presence_of(:title) }
+   it { is_expected.to belong_to(:user) }
  end

NoMethodError

错误提示:

Failures:

  1) CoursesController POST create create a course for user
     Failure/Error: expect(Course.last.user).to eq(user)

     NoMethodError:
       undefined method `user' for nil:NilClass
     # ./spec/controllers/courses_controller_spec.rb:102:in `block (3 levels) in <top (required)>'
     

原因及解法:
context "when course have a title " do 里面定义了 before { sign_in_user},而 create a course for user 这个测试并没有包含在 context 里面,所以会找不到用户。
修改 spec/controllers/courses_controller_spec.rb

spec/controllers/courses_controller_spec.rb
   context "when course have a title " do
      before { sign_in_user}
      it "create a new course record" do
        ……略
      end

      it "redirect to courses_path" do
        ……略
      end
-    end

      it "create a course for user" do
        course = FactoryGirl.build(:course)
        post :create, params: { course: FactoryGirl.attributes_for(:course)}
        expect(Course.last.user).to eq(user)
      end
+  end

RuntimeError

错误提示:

  1) CoursesController PUT update when course have title assign @course
     Failure/Error: sign_in user

     RuntimeError:
       Could not find a valid mapping for #<Course id: 1, title: "Course Title", description: "Couese Description.", created_at: "2016-09-21 04:20:00", updated_at: "2016-09-21 04:20:00", user_id: 1>

     # /.rvm/gems/ruby-2.3.1/gems/devise-4.2.0/lib/devise/mapping.rb:44:in `find_scope!'

     # /.rvm/gems/ruby-2.3.1/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:74:in `sign_in'

     # ./spec/support/macros.rb:2:in `sign_in_user'

     # ./spec/controllers/courses_controller_spec.rb:148:in `block (3 levels) in <top (required)>'

     

原因及解法:
创建了错误的数据类型。
修改 spec/controllers/courses_controller_spec.rb

spec/controllers/courses_controller_spec.rb
  describe "PUT update" do

    let (:user) { FactoryGirl.create(:user)}
-   let (:user) { FactoryGirl.create(:course)}
+   let (:course) { FactoryGirl.create(:course)}
    before { sign_in_user }

NoMethodError

错误提示:

Failures:

  1) CoursesController DELETE destroy behaves like require_sign_in redirects to login page
     Failure/Error: @course = current_user.courses.find(params[:id])

     NoMethodError:
       undefined method `courses' for nil:NilClass
     Shared Example Group: "require_sign_in" called from ./spec/controllers/courses_controller_spec.rb:222
     # ./app/controllers/courses_controller.rb:40:in `destroy'
     

原因及解法:
controller 的限制尚未将 destroy action 添加进来
修改 app/controllers/courses_controller.rb

app/controllers/courses_controller.rb
  class CoursesController < ApplicationController

-     before_action :authenticate_user!, only: [:new, :create, :edit, :update,]

+     before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]

ActiveRecord::RecordNotFound

错误提示:

 Failures:

  1) CoursesController DELETE destroy assigns @course
     Failure/Error: @course = current_user.courses.find(params[:id])

     ActiveRecord::RecordNotFound:
       Couldn't find Course with 'id'=1 [WHERE "courses"."user_id" = ?]

原因及解法:
使用 FactoryGirl 创建 course 的时候没有为其指定 user。
修改 spec/controllers/courses_controller_spec.rb

spec/controllers/courses_controller_spec.rb
  describe "DELETE destroy" do

      let(:user) { FactoryGirl.create(:user)}
-     let!(:course) { FactoryGirl.create(:course)}
+     let!(:course) { FactoryGirl.create(:course, user: user )}

NoMethodError

错误提示:

Failures:

  1) user sign out sign_out the user
     Failure/Error: navbar.sign_in_user user.email

     NoMethodError:
       private method `sign_in_user' called for #<PageObjects::Application::Navbar:0x007fe48deb8448 @_routes=nil>
       Did you mean?  sign_in
                      sign_out_user
     # ./spec/features/user_sign_out_spec.rb:8:in `block (2 levels) in <top (required)>'

原因及解法:
spec/support/page_objects/application/navbar.rb 里的 def sign_out 定义有错
修改 spec/support/page_objects/application/navbar.rb

spec/controllers/courses_controller_spec.rb
-      def user_sign_out(email)
+      def sign_out(email)
        user_dropdown(email).click_on "Logout"
      end

wrong number of arguments

错误提示:

Failures:

  1) user create course valid
     Failure/Error: course_form.create course.title course.description

     ArgumentError:
       wrong number of arguments (given 1, expected 0)
     # ./spec/features/user_create_course_spec.rb:6:in `block (2 levels) in <top (required)>'

原因及解法:
在创建 course 的时候代码少了一个逗号分隔符
修改 spec/features/user_create_course_spec.rb

spec/features/user_create_course_spec.rb
  scenario "valid" do
    course = build_stubbed(:course)
-   course_form.create course.title course.description
+   course_form.create(course.title, course.description)
    expect(page).to have_text(course.title)
  end

Could not find shared examples

错误提示:

/.rvm/gems/ruby-2.3.1/gems/rspec-core-3.5.3/lib/rspec/core/example_group.rb:370:in `find_and_eval_shared': Could not find shared examples "require_sign_in" (ArgumentError)

原因及解法:
因为尚未建立该 example
新增 spec/support/shard_examples.rb

spec/support/shard_examples.rb
shared_examples "require_sign_in" do
  it "redirects to login page" do
    sign_out_user
    action
    expect(response).to redirect_to new_user_session_path
  end
end

got: nil

错误提示:

Failures:

  1) CoursesController GET show assigns @course
     Failure/Error: expect(assigns[:courses]).to eq(course)

       expected: #<Course id: 1, title: "Course Title", description: "Couese Description.", created_at: "2016-09-21 12:12:29", updated_at: "2016-09-21 12:12:29", user_id: 1>

            got: nil

       (compared using ==)

原因及解法:
单复数使用错误
修改spec/controllers/courses_controller_spec.rb

spec/controllers/courses_controller_spec.rb
  describe "GET show" do
    it "assigns @course" do
      course = FactoryGirl.create(:course)
      get :show, params: { id: course.id }
-      expect(assigns[:courses]).to eq(course)
+      expect(assigns[:course]).to eq(course)

    end

TypeError

错误提示:

Failures:

  1) CoursesController GET new when user login  assign @course
     Failure/Error: expect(assigns(:course)).to be_instance_of(course)

     TypeError:
       class or module required
     # ./spec/controllers/courses_controller_spec.rb:46:in `block (4 levels) in <top (required)>'

原因及解法:
类名定义错误,首字母应该大写
修改spec/controllers/courses_controller_spec.rb

spec/controllers/courses_controller_spec.rb
  describe "GET new" do
      ……略
      it "assign @course" do
         expect(assigns(:course)).to be_new_record
-        expect(assigns(:course)).to be_instance_of(course)
+        expect(assigns(:course)).to be_instance_of(Course)
      end