Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble differentiating Rspec's 'let' vs 'let!'

Tags:

let

rspec

I have read the rspec docs and have searched a number of other places but I am having a difficult time grasping the difference between Rspec's let and let!

I've read that let isn't initialized until it's needed and that its value is only cached per example. I've also read that let! forces the variable into immediate existence, and forces invocation for each example. I guess since I'm new, I'm having a difficult time seeing how this relates to the following examples. Why does :m1 need to be set with let! to assert m1.content is present on the page, but :user can be set with letto assert that the page contains text: user.name?

  subject { page }

  describe "profile page" do
    let(:user) { FactoryGirl.create(:user) }
    let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
    let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }

    before { visit user_path(user) }

    it { should have_selector('h1',    text: user.name) }
    it { should have_selector('title', text: user.name) }

    describe "microposts" do
      it { should have_content(m1.content) }
      it { should have_content(m2.content) }
      it { should have_content(user.microposts.count) }
    end
  end

  describe "after saving the user" do
    before { click_button submit }
    let(:user) { User.find_by_email('[email protected]') }

    it { should have_selector('title', text: user.name) }
    it { should have_success_message('Welcome') } 
    it { should have_link('Sign out') }
  end
like image 810
KMcA Avatar asked Jul 01 '13 15:07

KMcA


People also ask

What is the difference between let and let in RSpec?

You can use let! to force the method's invocation before each example. The difference between let, and let! is that let! is called in an implicit before block. So the result is evaluated and cached before the it block.

What is let in RSpec?

What does let do? let generates a method whose return value is memoized after the first call. This is known as lazy loading because the value is not loaded into memory until the method is called. Here is an example of how let is used within an RSpec test.

How do I run a specific test in RSpec?

Running tests by their file or directory names is the most familiar way to run tests with RSpec. RSpec can take a file name or directory name and run the file or the contents of the directory. So you can do: rspec spec/jobs to run the tests found in the jobs directory.

How do you mock an object in RSpec?

Normally you want to use a Mock Object when you want to delegate some functionality to other object but you don't want to test the real functionality on your current test, so you replace that object with other that is easier to control. Let's call this object "dependency"...


1 Answers

Because the before block is calling visit user_path(user) the user value gets initialized there and RSpec will visit that page. If the :m1 :m2 were not using let! then the visit would yield no content making

it { should have_content(m1.content) }
it { should have_content(m2.content) }

fail because it expects the microposts to be created before the user visits the page. let! allows the microposts to be created before the before block gets called and when the tests visit the page the microposts should've already been created.

Another way to write the same tests and have them pass is doing the following:

describe "profile page" do
  let(:user) { FactoryGirl.create(:user) }
  let(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
  let(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }

  before do
    m1
    m2
    visit user_path(user)
  end

calling the variables m1 and m2 before visit user_path(user) causes them to be initialized before the page is visited and causing the tests to pass.

UPDATE This small example would make more sense:

In this example we are calling get_all_posts which just returns an array of posts. Notice that we're calling the method before the assertion and before the it block gets executed. Since post doesn't get called until the assertion is executed.

def get_all_posts
  Post.all
end

let(:post) { create(:post) }

before { @response = get_all_posts }

it 'gets all posts' do 
  @response.should include(post)
end

by using let! the post would get created as soon as RSpec sees the method (before the before block) and the post would get returned in the list of Post

Again, another way to do the same would be to call the variable name in the before block before we call the method

before do
  post
  @response = get_all_posts
end

as that will ensure that the let(:post) block gets called before the method itself is called creating the Post so that it gets returned in the Post.all call

like image 173
Leo Correa Avatar answered Oct 12 '22 08:10

Leo Correa