问题:实作rspec出现的错误

测试controller时候的bug

Failures:

  1) CoursesController GET index assigns @courses and render template
     Failure/Error: expect(assigns[:course]).to eq([course1,course2])

       expected: [#<Course id: 1, title: "foo", description: "bar", created_at: "2016-12-17 03:04:56", updated_at: "20...e: "bar", description: "foo", created_at: "2016-12-17 03:04:56", updated_at: "2016-12-17 03:04:56">]
            got: nil

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

Finished in 0.03173 seconds (files took 1.2 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/courses_controller_spec.rb:5 # CoursesController GET index assigns @courses and render template

原因是index里面的course应该是复数courses,错误版本expect(assigns[:course]).to eq([course1,course2]) 正确版本expect(assigns[:courses]).to eq([course1,course2])

使用factory_girl生成假文件的错误(spec/support/factory_girl.rb): 错误版本:

Rspec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

正确版本(rspec的前面链两个字母需要大写):

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

One assertion per test 原則:在寫測試的時候,有一個測試的黃金原則:One assertion per test,也就是一個測試一次只測一件事。 测试new controller时候的bug:

CoursesController GET new assigns @course
     Failure/Error: expect(assigns[:course]).to eq(course)
       expected: #<Course id: nil, title: "Course name", description: "Description", created_at: nil, updated_at: nil>
            got: #<Course id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
       (compared using ==)
       Diff:
       @@ -1,7 +1,7 @@
       -#<Course:0x007ff87d1cc3e8
       +#<Course:0x007ff87d1a6d00
         id: nil,
       - title: "Course name",
       - description: "Description",
       + title: nil,
       + description: nil,
         created_at: nil,
         updated_at: nil>

期望是否相等的时候左边不等于右边 错误写法expect(assigns[:course]).to eq(course) 正确写法expect(assigns[:course]).to be_a_new(Course)

测试controller create action的错误

ArgumentError:
       `change` requires either an object and message (`change(obj, :msg)`) or a block (`change { }`). You passed an object but no message.

原因是change后面接的括号写错了。错误(),正确{}. 测试create的action的写法有所不同,在这里记录一下:

describe "assigns @course" do 
  it "create a new course record" do 
    course = build(:course)
    expect do 
    post :create, params: { :course => attributes_for(:course) }
    end.to change{Course.count}.by(1)
  end
  it "render template" do 
    course = build(:course)
    post :create, params: { :course => attributes_for(:course) }
    expect(response).to redirect_to courses_path
  end
end    

测试标题不能为空的写法

  it "doesn't create a new record when course doesn't have title" do
  course = build(:course)
  expect do 
    post :create, params: { :course => { :title => nil } }
    end.to change{Course.count}.by(0)
  end
  it "render template when course doesn't have title" do
  course = build(:course)
  post :create, params: { :course => { :title => nil } }
  expect(response).to render_template("new")
  end

简写create expect的方法: expect{post :create, params: { course: attributes_for(:course) }}.to change{Course.count}.by(1) 尝试写法: ` expect{post :create, course: :course}.to change{Course.count}.by(1) 错误信息:undefined method permit' for "course":String 这样的写法,会把course当成course里面的一个栏位。

在加入user之后,用FactoryGirl生成email和password出现错误:

 CoursesController POST create when course has title redirects to courses_path
     Failure/Error: course = build(:course)
 ActiveRecord::RecordInvalid:
       Validation failed: Email has already been taken

原因是生成email的时候没有批量生成:sequence(:email) { |n| "user#{}@example.com" } 正确的是这样的sequence(:email) { |n| "user#{n}@example.com" } 下面是判断create action是否跟user关联的

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

在测试只有创始人才能修改course的rspec出现的错误

 NameError:
       undefined local variable or method `not_author' for #<RSpec::ExampleGroups::CoursesController::GETEdit::SignInNotAuthor:0x007fc47bda9b88>

原因是只创建了author,没有创建not_author这个方法。下面写一个测试的方法:

describe "GET edit" do 
 let(:author) { create(:user)}
 let(:not_author) { create(:user)}
   context "sign in author" do
    before { sign_in author }
     it "assigns @course" do 
       course = create(:course, user: author)
       get :edit, id: course.id
       expect(assigns[:course]).to eq(course)
     end
     it "render template" do 
       course = create(:course, user: author)
       get :edit, id: course.id
       expect(response).to render_template("edit")
     end
   end
   context "sign in not author" do 
     before { sign_in not_author }
      it "raises an error" do 
        course = create(:course, user: author)
        expect do 
          get :edit, id: course.id 
        end.to raise_error ActiveRecord::RecordNotFound
      end
   end
 end

update的bugCould not find shared examples "require_course_owner" (ArgumentError) 原来是没有定义它的方法,需要创建两个文件touch spec/support/macros.rband touch spec/support/shard_examples.rb

###macros.rb
  
def sign_in_user
  sign_in user
end

def sign_out_user
  sign_out user
end
###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


shared_examples "require_course_owner" do
  it "can find course" do
    expect{ action }.to raise_exception ActiveRecord::RecordNotFound
  end
end

update action的错误 undefined local variable or method course’ for #<RSpec::ExampleGroups::CoursesController::PUTUpdate::WhenCourseHaveTitle:0x007fb3f1ab8928> 错误版:expect(assigns[:course]).to eq(course) 正确版:expect(assigns[:course]).to eq(course_with_owner)` update针对是否是创建者进行test

    describe 'PUT update' do
        let(:user) { create(:user) }
        let(:course_with_owner) { create(:course, user: user) }
        let(:course_without_owner) { create(:course) }
        before { sign_in user }
        context 'when course  have title' do
            it 'assigns @course' do
                put :update, params: { id: course_with_owner.id, course: { title: 'title', description: 'description' } }
                expect(assigns[:course]).to eq(course_with_owner)
            end
            it 'change value' do
                put :update, params: { id: course_with_owner.id, course: { title: 'title', description: 'description' } }
                expect(assigns[:course].title).to eq('title')
                expect(assigns[:course].description).to eq('description')
            end
            it 'redirect_to course_path' do
                put :update, params: { id: course_with_owner.id, course: { title: 'title', description: 'description' } }
                expect(response).to redirect_to course_path(course_with_owner)
            end
        end
        context 'when course not have title' do
            it 'assigns @course' do
                put :update, params: { id: course_with_owner.id, course: { title: nil, description: 'description' } }
                expect(assigns[:course].title).not_to eq(nil)
                # expect(course.description).not_to eq("description")
            end
            it 'render template' do
                put :update, params: { id: course_with_owner.id, course: { title: nil, description: 'description' } }
                expect(response).to render_template('edit')
            end
        end
        it_behaves_like 'require_course_owner' do
            let(:action) do
                put :update, id: course_without_owner.id, course: { title: 'title', description: 'description' }
            end
        end
        it_behaves_like 'require_sign_in' do
            let(:action) do
                put :update, id: course_without_owner.id, course: { title: 'title', description: 'description' }
            end
        end
    end     

destroy action的test

describe "DELETE destroy" do
    let(:author) { create(:user) }
    let(:not_author) { create(:user) }
      context "when sign in author" do
        before { sign_in author }
        it "assigns @course" do
          course = create(:course, user: author)
          delete :destroy,params: { id: course.id }
          expect(assigns[:course]).to eq(course)
        end
        it "delete record" do
          course = create(:course, user: author)
          expect{delete :destroy, params: {id: author}}.to change{ Course.count }.by(-1)
        end
        it "render template" do
          course = create(:course, user: author)
          delete :destroy, id:course.id
          expect(response).to redirect_to courses_path
        end
      end
      context "when sign in not author" do
        before { sign_in not_author }
        it "raises an error" do
          course = create(:course, user: author)
          expect do
            delete :destroy,params: {id:course.id}
          end.to raise_error ActiveRecord::RecordNotFound
        end
      end
    end

第二版本

  describe "DELETE dstroy" do

    let(:user) { FactoryGirl.create(:user) }
    let!(:course_with_owner) { FactoryGirl.create(:course, user: user) }
    let(:course_without_owner) { FactoryGirl.create(:course) }
    before { sign_in_user }

    it "assigns @course" do
      delete :destroy, id: course_with_owner.id
      expect(assigns[:course]).to eq(course_with_owner)
    end

    it "delete a record" do
      expect { delete :destroy, id: course_with_owner.id }.to change{ Course.count }.by(-1)
    end

    it "redirect to courses_path" do
      delete :destroy, id: course_with_owner.id
      expect(response).to redirect_to courses_path
    end

    it_behaves_like "require_course_owner" do
      let (:action) {
        delete :destroy, id: course_without_owner.id
      }
    end

    it_behaves_like "require_sign_in" do
      let (:action) {
        delete :destroy, id: course_without_owner.id
      }
    end
  end