【问题标题】:Rspec expect passing when it should notRspec期望在不应该通过的时候通过
【发布时间】:2016-03-05 02:02:02
【问题描述】:

当我运行以下测试时

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。我不认为这是由于污染,因为即使我单独运行测试(焦点),这种影响仍然存在。

【问题讨论】:

  • 您是否在规范之间清理数据库?否则是由于测试污染github.com/DatabaseCleaner/database_cleaner
  • “第二个期望”到底是什么? [] 在技术上是该文件中的第二个还是第二个规范中的第二个?请添加一些行号和实际错误输出。
  • 如何添加行号?我对其进行了编辑以进行澄清。
  • 你能添加你的控制器的代码吗?

标签: ruby-on-rails ruby-on-rails-4 rspec rspec-rails


【解决方案1】:

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

@lessons = Lesson.all 

请记住,这会返回一个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! 在运行示例之前创建课程

【讨论】:

  • 这太棒了,一个很棒的解释!话虽如此,我可以假设期望语句最后运行吗?在expect(a).to eq(b) 中,b 将首先被评估然后a? (我认为这将弥补我所拥有的任何其他差距。)
  • 我已经超出了我的深度,但我认为首先评估expect(a),然后从.to(eq(b))开始。对于该部分,它评估eq(b),然后将结果传递给.to(...)。触发 SQL 查询的是.to(...)(因为它将数组与 AR::Relation 进行比较)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多