Juan Dominguez
Juan Dominguez

Reputation: 75

Rspec expect passing when it should not

When I run the following test

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

The second expect, expect(assigns[:lessons]).to eq([lesson1, lesson2]), fails with expected: [#<Lesson id:...>, #<Lesson id:...>] got: #<ActiveRecord::Relation []>.

But then, when I run the following test and it all passes

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

I am wondering why is it that the second test does not fail? I was expecting the second spec to also fail with the same reason as the first one.

I believe it might be due to the let statement.

With that said, I am running rspec-rails, factory_girl_rails and Rails 4. I don't believe it is due to contamination because this effect still occurs even when I run the test in isolation (focus).

Upvotes: 1

Views: 245

Answers (1)

rmosolgo
rmosolgo

Reputation: 1874

First, I'm guessing your controller has some code like this:

@lessons = Lesson.all 

Remember, that returns an ActiveRecord::Relation which may not actually hit the database until the last moment it needs to. Also, once an ActiveRecord::Relation fetches its results, it will not re-fetch them unless you call .reload.

Secondly, remember how let works. Code for a let isn't evaluated until you try to access that a variable. So, you get a situation like this:

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 

Third, when you turn an ActiveRecord::Relation into an Array, it executes the real database query (in this case, select * from lessons).

With those things in mind, we can contrast the two test cases.

In the first case, lessons are fetched from the database before the lessons are actually created:

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

In the second case, the lessons are created first, then the database query is executed:

  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])

To demonstrate this, here is an example that should pass:

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)

Also, you could use RSpec's let! to create the lessons before running the example.

Upvotes: 1

Related Questions