Perché aggiungere “sleep 1” in un hook dopo questo test Rspec / Capybara per passare?

Sto usando rails 4.0.5, rspec 2.14.1, capybara 2.2.1, capybara-webkit 1.1.0 e database_cleaner 1.2.0. Sto osservando un comportamento strano con il seguente test di funzionalità (che simula un utente che visualizza un commento su un post, passa con il mouse su un’icona per far apparire un menu e fa clic su una voce di menu per eliminare il commento):

let(:user){create(:user)} let(:post){create(:post, author: user)} let!(:comment){create(:comment, post: post, author: user)} ... it "can delete a comment" do assert(page.has_css? "#comment-#{comment.id}") find("#comment-#{comment.id}-controls").trigger(:mouseover) find("#comment-#{comment.id} .comment-delete a").click assert(page.has_no_css? "#comment-#{comment.id}") end 

Questo test fallisce circa l’80% delle volte, sempre a causa di alcuni record recuperati dal database come nil – Ottengo NoMethodError: undefined method X for nil:NilClass , per vari valori di X. A volte il nil è il commento che viene cancellato, a volte è il post a cui è collegato il commento, a volte è l’autore del commento / post.

Se aggiungo il sleep 1 alla fine del test, passa:

 it "can delete its own comment" do assert(page.has_css? "#comment-#{comment.id}") find("#comment-#{comment.id}-controls").trigger(:mouseover) find("#comment-#{comment.id} .comment-delete a").click assert(page.has_no_css? "#comment-#{comment.id}") sleep 1 end 

Passa anche se inserisco il sleep 1 in un after block.

Qualche idea sul perché ottengo questi NoMethodErrors e / o perché il test passa se riesco a dormire per un secondo dopo che tutto il lavoro è finito?

Sospetto che nella tua applicazione sia ansible che il commento scompaia dalla pagina (che è l’ultima cosa che stai affermando) prima che venga cancellato dal database. Ciò significa che il test può essere pulito prima che avvenga la cancellazione. Se questo è il caso, puoi risolvere il problema aspettando che la cancellazione avvenga alla fine del test. Ho questo metodo in giro (una reimplementazione di un metodo che è stato rimosso da Capybara 2 ma è ancora a volte necessario )

 def wait_until(delay = 1) seconds_waited = 0 while ! yield && seconds_waited < Capybara.default_wait_time sleep delay seconds_waited += 1 end raise "Waited for #{Capybara.default_wait_time} seconds but condition did not become true" unless yield end 

quindi posso farlo

 wait_until { Comment.count == 0 } 

nei test

Un altro approccio consiste nell'aggiungere il middleware Rack che blocca le richieste fatte al termine del test. Questo approccio è descritto in dettaglio qui: http://www.salsify.com/blog/tearing-capybara-ajax-tests ho visto che fa un ottimo lavoro per affrontare la perdita di dati in una suite di dimensioni di specifiche di RSpec.

https://github.com/jnicklas/capybara#asynchronous-javascript-ajax-and-friends

Quando lavori con JavaScript asincrono, potresti incontrare situazioni in cui stai tentando di interagire con un elemento che non è ancora presente nella pagina. Capibara si occupa automaticamente di questo aspettando che gli elementi appaiano sulla pagina.