2013-07-01 20 views
6

Ho letto l'rspec docs e hanno cercato un certo numero di altri posti, ma io sto avendo un momento difficile cogliere la differenza tra Rspec di let e let!Problemi di differenziazione di Rspec "let" vs "let!"

Ho letto che let non è inizializzato fino a quando è necessario e che la sua il valore è solo memorizzato nella cache per esempio. Ho anche letto che let! obbliga la variabile all'esistenza immediata e forza l'invocazione per ogni esempio. Credo che da quando sono nuovo, sto avendo un momento difficile vedere come questo si riferisce ai seguenti esempi. Perché è necessario impostare per affermare che m1.content è presente nella pagina, ma è possibile impostare :user con let per affermare che la pagina contiene 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 

risposta

12

Perché la prima del blocco sta chiamando visit user_path(user) il valore utente viene inizializzato lì e RSpec visiterà quella pagina. Se il :m1:m2 non utilizzavano let! poi la visita produrrebbe alcun contenuto rendendo

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

falliscono perché si aspetta che i microposts da creare prima che l'utente visita la pagina. let! consente di creare i micropost prima che venga chiamato il blocco precedente e quando i test visitano la pagina, i micropost devono essere già stati creati.

Un altro modo di scrivere gli stessi test e li hanno passano sta facendo il seguente:

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 

chiamando le variabili m1 e m2 prima visit user_path(user) provoca loro di essere inizializzati prima che la pagina viene visitata e causando i test passaggio.

UPDATE Questo piccolo esempio avrebbe più senso:

In questo esempio stiamo chiamando get_all_posts che solo restituisce un array di tutti i messaggi. Si noti che stiamo chiamando il metodo prima dell'asserzione e prima che venga eseguito il blocco it. Dal momento che il post non viene chiamato fino a quando l'asserzione non viene eseguita.

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 

utilizzando let! post otterrebbe creato non appena RSpec vede il metodo (prima del blocco before) e il post otterrebbe tornato nella lista dei Post

Anche in questo caso, un altro modo di fare il stesso sarebbe chiamare il nome della variabile nel blocco prima prima di chiamare il metodo

before do 
    post 
    @response = get_all_posts 
end 

come in grado di garantire che il blocco let(:post) viene chiamato prima del metodo stesso è cal led creando lo in modo che venga restituito nella chiamata

+0

Questo ha perfettamente senso. Quindi, inviando l'oggetto (m1) un messaggio (contenuto) non è abbastanza usando let perché m1 non è ancora stato inizializzato? – KMcA

+0

Beh, non è questo il motivo per cui il test fallirebbe. Il motivo per cui il test fallisce è perché hai già "visitato la pagina" prima di creare i micropost e quindi la pagina non ha contenuto. Potrei aggiornare la mia risposta per essere un po 'più chiaro su questo se hai ancora dubbi? –

+0

Bene, dopo aver esaminato nuovamente la mia domanda ("dopo aver salvato l'utente") di nuovo, in che modo il secondo test potrebbe verificare 'user.name'? – KMcA

0

La chiave per differenziare è proprio come rspec esegue i passaggi.

un'occhiata al codice di nuovo:

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

se usiamo lasciamo invece di lasciare !, M1 e M2 non sono creati in questo momento. Rspec quindi fa una visita e la pagina viene caricata, ma ovviamente non c'è né m1 né m2 sulla pagina.

Quindi, se chiamiamo m1 e m2, verranno creati in memoria. Ma è già troppo tardi perché la pagina non verrà caricata di nuovo a meno che non lo facciamo volutamente. Quindi qualsiasi test dell'interfaccia utente sulla pagina si tradurrà in un errore.