timpone
timpone

Reputation: 19929

FactoryGirl, Rspec, let, and before issues

I am using some of these tools for the first time. I have read through the docs but wanted to ask here exactly what I'm trying to achieve.

I have a set of users that I want to test some actions I can do in a controller spec. When each user is created, there are a set of callbacks that take place to create associated objects.

I'd like to have access to these user instances and the associated objects of that ActiveRecord class. So for example, a user will have a set of lists so I'd like to be able to call user1.lists for example.

Also, I'd like to isolate this setup at the top and use either let's or a before black. It seems that just calling let like this:

# will test that get_count_for_list will return 5
describe ApiController do

  # why same name - seems really confusing!
  let(:user) { FactoryGirl.create(:user) }
  let(:user2) { FactoryGirl.create(:user2) }

doesn't call the associated callbacks. Is this correct? Or is it possibly a timing issue?

I like the syntax of using let and being able to access these objects in my ExampleGroups such as user.id but can't access user.lists. Currently I am doing something like:

# will test that get_count_for_list will return 5

describe ApiController do

  # why same name - seems really confusing!
  let(:user) { FactoryGirl.create(:user) }
  let(:user2) { FactoryGirl.create(:user2) }
  let(:user3) { FactoryGirl.create(:user3) }
  
  before do
    FactoryGirl.create(:user2)
    FactoryGirl.create(:user3)
  end

but feel that there has to be a better way. Am I creating these user's twice?

thx

edit 1

I've isolated the code in question here. The global_id value is created via a callback. It exists correctly in the db and can be accessed via the corresponding find_by_email's but using the user2 var's doesn't provide access.

require 'spec_helper'

# will test that get_count_for_list will return 5
describe ApiController do
  # why same name - seems really confusing!
  let!(:user) { FactoryGirl.create(:user) }
  let!(:user2) { FactoryGirl.create(:user2) }
  let!(:user3) { FactoryGirl.create(:user3) }


  
  before do
    session[:user_id]=user.id # works
  end

  describe 'FOLLOW / UNFOLLOW options' do
    it 'shall test the ability to follow another user' do
      puts "user1: " + user.global_id.to_s # doesn't output anything
      u2=User.find_by_email('[email protected]') # corresponds to user2
      post :follow, :global_id => user2.global_id # doesn't work
      #post :follow, :global_id => u2.global_id  #works


      u3=User.find_by_email('[email protected]')
      puts "user_3" + u3.global_id.to_s # outputs correct value

      post :follow, :global_id => user3.global_id  #doesn't work
      #post :follow, :global_id => u3.global_id # works


      post :unfollow, :global_id => user.following.sample(1)
      response.code.should eq('200')
    end
  end
end

Upvotes: 16

Views: 15682

Answers (2)

ballPointPenguin
ballPointPenguin

Reputation: 1156

I just solved a similar sounding problem. My user authentication spec was not passing using 'let'.

my broken spec:

describe "signin" do
  before { visit signin_path }

  describe "with valid information", :js => true do
    let(:user) { FactoryGirl.create(:user) }
    before do
      fill_in "email", with: user.email
      fill_in "password", with: user.password
      click_button "Log In"
    end
    it { should have_selector('a', text: "#{user.first} #{user.last}") }
  end
end

This was not working. The log-in authentication was failing as if the user record was not actually in the database when my sessions controller tries to authenticate it. I tried replacing let with let! but that did not fix anything. Reading this post, and the link above explaining let and let! I realized that I should not be using let for this spec. Now I have a passing spec:

describe "signin" do
  before { visit signin_path }

  describe "with valid information", :js => true do
    user = FactoryGirl.create(:user)
    before do
      fill_in "email", with: user.email
      fill_in "password", with: user.password
      click_button "Log In"
    end
    it { should have_selector('a', text: "#{user.first} #{user.last}") }
  end
end

Upvotes: 3

luacassus
luacassus

Reputation: 6720

Check the rspec doc: https://www.relishapp.com/rspec/rspec-core/v/2-11/docs/helper-methods/let-and-let

Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked. You can use let! to force the method's invocation before each example.

In other words if you use let along with factory_girl a record will not be created before let-variable invocation.

The correct code is:

# will test that get_count_for_list will return 5
describe ApiController do

  # why same name - seems really confusing!
  let!(:user) { FactoryGirl.create(:user) }
  let!(:user2) { FactoryGirl.create(:user2) }

Upvotes: 29

Related Questions