2013-05-09 11 views
20

Ho la seguente (semplificato) Rotaie Preoccupazione:Rails & RSpec - Preoccupazioni metodi di prova di classe

module HasTerms 
    extend ActiveSupport::Concern 

    module ClassMethods 
    def optional_agreement 
     # Attributes 
     #---------------------------------------------------------------------------- 
     attr_accessible :agrees_to_terms 
    end 

    def required_agreement 
     # Attributes 
     #---------------------------------------------------------------------------- 
     attr_accessible :agrees_to_terms 

     # Validations 
     #---------------------------------------------------------------------------- 
     validates :agrees_to_terms, :acceptance => true, :allow_nil => :false, :on => :create 
    end 
    end 
end 

non riesco a capire un buon modo per testare questo modulo in RSpec tuttavia - se mi basta creare una classe fittizia, ricevo errori record attivi quando provo a verificare che le convalide funzionino. Qualcun altro ha affrontato questo problema?

risposta

39

Verificare RSpec shared examples.

questo modo si può scrivere il seguente:

# spec/support/has_terms_tests.rb 
shared_examples "has terms" do 
    # Your tests here 
end 


# spec/wherever/has_terms_spec.rb 
module TestTemps 
    class HasTermsDouble 
    include ActiveModel::Validations 
    include HasTerms 
    end 
end 

describe HasTerms do 

    context "when included in a class" do 
    subject(:with_terms) { TestTemps::HasTermsDouble.new } 

    it_behaves_like "has terms" 
    end 

end 


# spec/model/contract_spec.rb 
describe Contract do 

    it_behaves_like "has terms" 

end 
+0

Questa è chiaramente la migliore risposta. Uno può essere esplicito nel caso Dummy E testare la stessa API nelle specifiche della classe genitore. Questo fa la differenza quando si ha a che fare con qualsiasi API "flessibile" (leggi: method_missing). Ci sono semplicemente alcuni casi a cui non si può pensare fino a quando non sono in uso in una classe "reale" (non fittizia), e gli esempi condivisi faranno un buon lavoro nell'esercitare il codice in ogni contesto necessario. – winfred

+0

Si verifica quando il modulo aggiunge attributi dinamici. Supponiamo che il tuo modulo permetta il metodo di classe: 'allow_upload: csv', che aggiunge metodi come' csv_file_path' e 'csv_file_size'. Ma hai un altro modello che chiama il file caricato ': attachment'. Ora la tua specifica "agisce come upload" fallirà perché uno sta aggiungendo 'csv_file_path' e uno ha' attachment_file_path'.Per questo motivo mi sento come se in molti casi andasse meglio per le tue esigenze utilizzare una classe fittizia per testare il comportamento del modulo come nella risposta di @Martijn – nzifnab

+1

@nzifnab per essere chiari, il modulo non sta aggiungendo il metodo, la classe bambino è esplicitamente. Se gli esempi condivisi sono appropriati, ecco una chiamata di giudizio specifica per la base di codice. Tuttavia, puoi ancora usarli in questo modo. È possibile passare le informazioni a loro, proprio come nella chiamata: 'it_behaves_like 'funge da upload',: csv' –

6

È possibile testare il modulo in modo implicito lasciando i test nelle classi che includono questo modulo. In alternativa, puoi includere altri moduli necessari nella tua classe dummy. Ad esempio, i metodi validates nei modelli AR sono forniti da ActiveModel::Validations. Così, per i test:

class DummyClass 
    include ActiveModel::Validations 
    include HasTerms 
end 

Ci possono essere altri moduli necessari per portare a base di dipendenze si basano su implicitamente nel modulo HasTerms.

+0

Sono d'accordo che il test implicita è facile, ma mi sento come se avessi bisogno di essere in grado per testare anche le cose in modo esplicito. Ciò è particolarmente rilevante quando si tratta di scrivere funzioni di classe che nessuna delle mie classi sta ancora utilizzando. – Bryce

+0

Qualcosa come il 'DummyClass' è l'alternativa che stai cercando. – rossta

5

ero alle prese con questo me stesso e evocato la soluzione seguente, che è molto simile idea di rossta ma utilizza una classe anonima, invece:

it 'validates terms' do 
    dummy_class = Class.new do 
    include ActiveModel::Validations 
    include HasTerms 

    attr_accessor :agrees_to_terms 

    def self.model_name 
     ActiveModel::Name.new(self, nil, "dummy") 
    end 
    end 

    dummy = dummy_class.new 
    dummy.should_not be_valid 
end 
+0

Questa è l'unica soluzione che ho trovato per funzionare. Quando vai con la classe dummy denominata, le tue specifiche iniziano a sanguinare l'una nell'altra se si chiama un metodo che modifica la classe, quindi la prossima specifica vedrà anche quella modifica di classe. Anche se il mio è più come 'let (: dummy_class) {Class.new (...)}' invece che inserirlo nel blocco 'it'. – nzifnab

2

Basandosi sulla risposta eccellente di Aaron K here, ci sono alcuni trucchetti che puoi usare con described_class che RSpec fornisce per rendere i tuoi metodi onnipresenti e far funzionare le fabbriche per te. Ecco un frammento di un esempio condiviso Recentemente ho fatto per un'applicazione:

shared_examples 'token authenticatable' do 
    describe '.find_by_authentication_token' do 
    context 'valid token' do 
     it 'finds correct user' do 
     class_symbol = described_class.name.underscore 
     item = create(class_symbol, :authentication_token) 
     create(class_symbol, :authentication_token) 

     item_found = described_class.find_by_authentication_token(
      item.authentication_token 
     ) 

     expect(item_found).to eq item 
     end 
    end 

    context 'nil token' do 
     it 'returns nil' do 
     class_symbol = described_class.name.underscore 
     create(class_symbol) 

     item_found = described_class.find_by_authentication_token(nil) 

     expect(item_found).to be_nil 
     end 
    end 
    end 
end 
3

Ecco un altro esempio (usando del factorygirl "creare" metodo" e shared_examples_for)

preoccupazione spec

#spec/support/concerns/commentable_spec 
require 'spec_helper' 
shared_examples_for 'commentable' do 
    let (:model) { create (described_class.to_s.underscore) } 
    let (:user) { create (:user) } 

    it 'has comments' do 
    expect { model.comments }.to_not raise_error 
    end 
    it 'comment method returns Comment object as association' do 
    model.comment(user, "description") 
    expect(model.comments.length).to eq(1) 
    end 
    it 'user can make multiple comments' do 
    model.comment(user, "description") 
    model.comment(user, "description") 
    expect(model.comments.length).to eq(2) 
    end 
end 

commentabile preoccupazione

module Commentable 
    extend ActiveSupport::Concern 
    included do 
    has_many :comments, as: :commentable 
    end 

    def comment(user, description) 
    Comment.create(commentable_id: self.id, 
        commentable_type: self.class.name, 
        user_id: user.id, 
        description: description 
       ) 
    end 

end 

e restraunt_spec possono sembrare qualcosa del genere (I'm n ot Rspec guru quindi non credo che il mio modo di scrivere le specifiche è buona - la cosa più importante è all'inizio):

require 'rails_helper' 

RSpec.describe Restraunt, type: :model do 
    it_behaves_like 'commentable' 

    describe 'with valid data' do 
    let (:restraunt) { create(:restraunt) } 
    it 'has valid factory' do 
     expect(restraunt).to be_valid 
    end 
    it 'has many comments' do 
     expect { restraunt.comments }.to_not raise_error 
    end 
    end 
    describe 'with invalid data' do 
    it 'is invalid without a name' do 
     restraunt = build(:restraunt, name: nil) 
     restraunt.save 
     expect(restraunt.errors[:name].length).to eq(1) 
    end 
    it 'is invalid without description' do 
     restraunt = build(:restraunt, description: nil) 
     restraunt.save 
     expect(restraunt.errors[:description].length).to eq(1) 
    end 
    it 'is invalid without location' do 
     restraunt = build(:restraunt, location: nil) 
     restraunt.save 
     expect(restraunt.errors[:location].length).to eq(1) 
    end 
    it 'does not allow duplicated name' do 
     restraunt = create(:restraunt, name: 'test_name') 
     restraunt2 = build(:restraunt, name: 'test_name') 
     restraunt2.save 
     expect(restraunt2.errors[:name].length).to eq(1) 
    end 
    end 
end 
Problemi correlati