Search code examples
ruby-on-railsruby-on-rails-4rspecrspec-rails

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


Solution

  • 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.