Reputation: 75
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
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