2011-09-24 6 views
66

Ho iniziato il mio viaggio con TDD in Rails e ho riscontrato un piccolo problema relativo ai test per le convalide dei modelli a cui non riesco a trovare una soluzione. Diciamo che ho un modello User,Rails 3.1, RSpec: convalida delle convalide del modello

class User < ActiveRecord::Base 
    validates :username, :presence => true 
end 

e un semplice test

it "should require a username" do 
    User.new(:username => "").should_not be_valid 
end 

Questa verifica correttamente la convalida presenza, ma cosa succede se voglio essere più preciso? Ad esempio, full_messages test sugli errori oggetto ..

it "should require a username" do 
    user = User.create(:username => "") 
    user.errors[:username].should ~= /can't be blank/ 
end 

mia preoccupazione per il tentativo iniziale (usando should_not be_valid) è che RSpec non produrrà un messaggio di errore descrittivo. Dice semplicemente "atteso valido? Per restituire falso, diventa vero". Tuttavia, il secondo esempio di test ha un piccolo inconveniente: utilizza il metodo create al posto del nuovo metodo per ottenere l'oggetto errors.

Vorrei che i miei test fossero più specifici su ciò che stanno testando, ma allo stesso tempo non devono toccare un database.

Qualcuno ha qualche input?

risposta

91

Per prima cosa vorrei dire che hai un nome malvagio.

In secondo luogo, CONGRATULAZIONI su di voi si sforzano in TDD con ROR Prometto una volta che vai avanti non guarderai indietro.

La soluzione rapida e sporca più semplice sarà quello di generare un nuovo modello valido prima di ciascuno dei vostri test come questo:

before(:each) do 
    @user = User.new 
    @user.username = "a valid username" 
end 

ma quello che suggerisco è di impostare le fabbriche per tutti i vostri modelli che genereranno un modello valido per te automaticamente e poi puoi confonderti con i singoli attributi e vedere se la tua convalida. Mi piace usare FactoryGirl per questo:

In pratica una volta ci si imposta il test sarebbe simile a questa:

it "should have valid factory" do 
    FactoryGirl.build(:user).should be_valid 
end 

it "should require a username" do 
    FactoryGirl.build(:user, :username => "").should_not be_valid 
end 

Oh ya e qui è a good railscast che spiega tutto meglio di me:

buona fortuna :)


UPDATE: Come di version 3.0 la sintassi per la ragazza fabbrica ha cambiato. Ho modificato il mio codice di esempio per riflettere questo.

+2

Grazie mille Matthew. C'è un modo per avvicinarmi all'errore che sto provando a testare? X.should_not be_valid sembra così generico per me, e chissà se qualcos'altro in fondo renderà il record invalido. Questo test fallirà quindi nel punto sbagliato. A proposito, penso di aver contrassegnato la tua risposta come accettata. Non io? – Feech

+7

Giusto, quindi questo è il motivo per cui discuto per le fabbriche. Scrivi il codice per produrre un utente valido una volta in un posto e poi scrivi un test per assicurarti che sia valido prima di tutti i singoli test che assicurano di poterlo invalidare. In questo modo se per qualche ragione cambi il tuo modello in modo che la fabbrica risulti più lunga produce un utente valido il test 'Factory.build (: user) .should be_valid' fallirà e tu saprai che devi aggiornare la tua fabbrica ... prendila ? (e sì hai accettato la mia risposta7) – Matthew

+0

spiegazione perfetta. Grazie ancora. – Feech

41

Un modo più semplice per verificare le convalide del modello (e molto altro ancora di record attivi) consiste nell'utilizzare una gemma come shoulda o remarkable.

Essi consentiranno alla prova come segue:

describe User 

    it { should validate_presence_of :name } 

end 
+1

Questo è buono per verificare di avere le associazioni nei modelli, ma attenzione che in realtà non tenterà di creare un utente senza un nome e verificarne la validità – brafales

+3

@brafales no in realtà, afaik è esattamente ciò che dovrebbe fare: proverà a creare l'oggetto con un nome vuoto e dovrebbe dare un errore. – nathanvda

+2

Hai ragione, sembra che ho letto il codice sbagliato https://github.com/thoughtbot/shoulda-matchers/blob/master/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb – brafales

0

ho tradizionalmente gestite specifiche di contenuti di errore in funzione o richieste specifiche.Così, per esempio, ho una specifica simile che io condenso qui di seguito:

Caratteristica Spec Esempio

before(:each) { visit_order_path } 

scenario 'with invalid (empty) description' , :js => :true do 

    add_empty_task         #this line is defined in my spec_helper 

    expect(page).to have_content("can't be blank") 

Allora, io ho il mio test modello spec se qualcosa è valida, ma poi il mio funzionalità specifica che verifica l'output esatto del messaggio di errore. Cordiali saluti, queste caratteristiche richiedono Capybara che può essere trovato here.

15

Prova questo:

it "should require a username" do 
    user = User.create(:username => "") 
    user.valid? 
    user.errors.should have_key(:username) 
end 
+0

Questo è il mio preferito, molto solido, controlla la chiave e non il messaggio, che è un dettaglio – ecoologic

+3

puoi semplicemente usare user = User.new (: username => "") per evitare di colpire db –

+0

@TaufiqMuhammadi 'new' non colpirà le convalide di livello db, per esempio un vincolo di indice di unicità. – mnort9

2

nella versione nuova RSpec, si dovrebbe usare aspettare invece dovrebbe, altrimenti otterrete avvertimento:

it "should have valid factory" do 
    expect(FactoryGirl.build(:user)).to be_valid 
end 

it "should require a username" do 
    expect(FactoryGirl.build(:user, :username => "")).not_to be_valid 
end 
+0

Dovresti usare anche i verbi del tempo presente invece dei nomi di esempio. Quanto sopra può essere riscritto come '" ha una factory valida "' e '" richiede un nome utente "'. – BrunoFacca

0

Come @nathanvda detto, vorrei approfittare di La gemma di Thoughtbot Shoulda Matchers. Con quel dondolio, puoi scrivere il tuo test nel modo seguente per testare la presenza, così come ogni messaggio di errore personalizzato.

RSpec.describe User do 

    describe 'User validations' do 
    let(:message) { "I pitty da foo who dont enter a name" } 

    it 'validates presence and message' do 
    is_expected.to validate_presence_of(:name). 
     with_message message 
    end 

    # shorthand syntax: 
    it { is_expected.to validate_presence_of(:name).with_message message } 
    end 

end