2013-07-01 2 views
6

Я прочитал rspec docs и обыскал ряд других мест, но я имею трудное время схватив разницу между Rspec-х let и let!Проблемы с дифференциацией Rspec 'let' vs 'let!'

Я читал, что let не инициализирован, пока это не нужно, и что его значение кэшируется только на пример. Я также читал, что let! заставляет переменную в непосредственном существовании и вызывает вызов для каждого примера. Думаю, с тех пор как я новичок, мне трудно понять, как это относится к следующим примерам. Почему :m1 необходимо установить с let!, чтобы утверждать, что m1.content присутствует на странице, но :user может быть установлен с let, чтобы утверждать, что страница содержит 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 

ответ

12

Поскольку перед тем блок вызова visit user_path(user) значение пользователь получает инициализирована там и RSpec будет посетить эту страницу. Если :m1:m2 не использовали let! то визит не даст никакого содержания решений

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

не потому, что он ожидает, что microposts будет создан до того, как пользователь посещает страницу. let! позволяет создавать микросоты до того, как будет вызван запрос перед тем, как будет вызван блок, и когда тесты посещают страницу, микросоты должны были быть созданы.

Другим способом, чтобы написать те же тесты, и они проходят делает следующее:

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 

вызывая переменные m1 и m2 перед тем visit user_path(user) заставляет их быть инициализированы до посещаются страницы и вызывая тесты на проходить.

UPDATE Этот небольшой пример будет иметь больше смысла:

В этом примере мы называем get_all_posts, который просто возвращает массив сообщений. Обратите внимание, что мы вызываем метод перед утверждением и перед тем, как будет выполнен блок it. Поскольку сообщение не вызывается до тех пор, пока не будет выполнено утверждение.

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 

с помощью let! пост получил бы создан, как только RSpec видит метод (до before блока) и пост будет получить вернулся в список Post

Опять же, еще один способ сделать то же самое было бы назвать имя переменной в блоке перед перед вызовом метода

before do 
    post 
    @response = get_all_posts 
end 

как будет гарантировать, что let(:post) блок вызывается прежде, чем сам метод кал привело к созданию Post, так что оно возвращается в Post.all. вызов

+0

Это имеет смысл. Итак, отправляя объект (m1), сообщения (содержимого) недостаточно, используя let, потому что m1 еще не инициализирован? – KMcA

+0

Ну, это не причина, почему тест потерпел неудачу. Причина, по которой тест терпит неудачу, заключается в том, что вы уже «посетили страницу» перед созданием микросообщений, и поэтому страница не содержала контента. Я мог бы обновить свой ответ, чтобы быть более понятным, если у вас все еще есть сомнения? –

+0

Хорошо, снова посмотрев на мой вопрос («после сохранения пользователя»), как второй тест сможет проверить «user.name»? – KMcA

0

Ключ для дифференциации - это то, как выполняется rspec.

Посмотрите еще раз код:

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

Если мы используем пусть вместо аренды !, m1 и m2 не создаются в данный момент. Затем Rspec посещает, и страница загружается, но, очевидно, на странице нет m1 или m2.

Итак, если мы будем называть m1 и m2, они будут созданы в памяти. Но уже слишком поздно, так как страница не будет загружена снова, если мы не намеренно это сделаем. Следовательно, любой тест UI на странице приведет к сбою.

Смежные вопросы