Вопрос

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('user@example.com') }

    it { should have_selector('title', text: user.name) }
    it { should have_success_message('Welcome') } 
    it { should have_link('Sign out') }
  end
Это было полезно?

Решение

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

Другие советы

The key to differentiate is just how rspec performs the steps.

Look at the code again:

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

If we use let instead of let!, m1 and m2 are not created at the moment. Rspec then does a visit and the page is loaded, but obviously there is no m1 nor m2 on the page.

So now if we call m1 and m2, they will be created in memory. But it's already too late since the page is not going to be loaded again unless we purposely do so. Hence any UI test on the page will result in failure.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top