Rspec 期待在不该通过的时候通过

Rspec expect passing when it should not

当我运行以下测试

RSpec.describe LessonsController, type: :controller do
 describe 'GET / index' do
    let(:lesson1) {FactoryGirl.create(:lesson)}
    let(:lesson2) {FactoryGirl.create(:lesson)}

    it 'returns an http success' do
      get :index
      expect(response).to be_success

    end

    it 'returns all the lessons' do
      get :index
      expect(assigns[:lessons]).to eq([])
      expect(assigns[:lessons]).to eq([lesson1, lesson2])

    end
  end
end

第二个期望 expect(assigns[:lessons]).to eq([lesson1, lesson2]) 失败并显示 expected: [#<Lesson id:...>, #<Lesson id:...>] got: #<ActiveRecord::Relation []>

但是,当我 运行 下面的测试全部通过时

RSpec.describe LessonsController, type: :controller do
 describe 'GET / index' do
    let(:lesson1) {FactoryGirl.create(:lesson)}
    let(:lesson2) {FactoryGirl.create(:lesson)}

    it 'returns an http success' do
      get :index
      expect(response).to be_success

    end

    it 'returns all the lessons' do
      get :index
      expect(assigns[:lessons]).to eq([lesson1, lesson2])

    end
  end
end

我想知道为什么第二次测试没有失败?我原以为第二个规范也会因与第一个规范相同的原因而失败。

我相信这可能是由于 let 语句。

话虽如此,我 运行宁 rspec-rails、factory_girl_rails 和 Rails 4. 我不认为这是到期的污染,因为即使我 运行 隔离测试(焦点),这种影响仍然会发生。

首先,我猜你的控制器有这样的代码:

@lessons = Lesson.all 

请记住,returns 和 ActiveRecord::Relation 可能要到最后一刻才会真正访问数据库。此外,一旦 ActiveRecord::Relation 获取其结果,它 将不会 重新获取它们,除非您调用 .reload.

其次,记住 let 是如何工作的。 let 的代码在您尝试访问该变量之前不会被评估。所以,你会遇到这样的情况:

describe "Something" do 
  let(:lesson) { Lesson.create! }

  it "makes a lesson" do 
    # right now there are 0 lessons 
    lesson
    # calling `lesson` caused the lesson to be created, 
    # now there is 1 lesson
  end 
end 

第三,当你把一个ActiveRecord::Relation变成一个数组时,它会执行真正的数据库查询(在本例中,select * from lessons)。

考虑到这些,我们可以对比这两个测试用例。

在第一种情况下,在实际创建课程之前从数据库中获取课程:

it 'returns all the lessons' do
  get :index
  # No lessons have been created yet 
  # `select * from lessons` returns no results 
  expect(assigns[:lessons]).to eq([])

  # `lessons` is cached. It won't query the database again 
  # calling `lesson1` and `lesson2` creates two lessons, but it's too late 
  # the result has already been cached as []
  expect(assigns[:lessons]).to eq([lesson1, lesson2])
end

第二种情况,先创建课程,然后执行数据库查询:

  get :index
  # calling `lesson1` and `lesson2` creates two lessons
  # then the AR::Relation runs a query and finds the two lessons
  expect(assigns[:lessons]).to eq([lesson1, lesson2])

为了证明这一点,这里有一个应该通过的例子:

get :index 
expect(assigns[:lessons]).to eq([])
# this causes the lessons to be created 
lessons = [lesson1, lesson2]
# use `.reload` to force a new query:
expect(assigns[:lessons].reload).to eq(lessons)

此外,您可以使用 RSpec 的 let! 创建课程 before 运行 示例。