2013-05-09 12 views
20

Mam następujący (uproszczony) Szyny dotyczą:Rails & RSpec - Badanie dotyczy metod klasy

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 

nie mogę wymyślić dobry sposób, aby przetestować ten moduł w RSpec jednak - jeśli po prostu stworzyć klasa dummy, otrzymuję aktywne błędy rekordów, gdy próbuję sprawdzić, czy sprawdzania poprawności działają. Czy ktokolwiek inny zmierzył się z tym problemem?

Odpowiedz

39

Sprawdź RSpec shared examples.

ten sposób można napisać następujące:

# 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

To zdecydowanie najlepsza odpowiedź. Można być jednoznacznym w przypadku manekina ORAZ przetestować to samo API w specyfikacjach klasy macierzystej. To robi różnicę, gdy mamy do czynienia z dowolnymi "elastycznymi" API (czytaj: method_missing). Są tylko niektóre przypadki, o których nie można myśleć, dopóki nie zostaną użyte w "prawdziwej" (nie-niemuskiej) klasie, a udostępnione przykłady wykonają dobrą robotę wykonywania kodu w każdym niezbędnym kontekście. – winfred

+0

To się rozpada, gdy moduł dodaje atrybuty dynamiczne. Załóżmy, że Twój moduł pozwala na metodę klasy: 'allow_upload: csv', która dodaje metody takie jak' csv_file_path' i 'csv_file_size'. Ale masz inny model, który wywołuje przesłany plik ': attachment'. Teraz twoja specyfikacja "działa jak upload" nie będzie działać, ponieważ dodaje się 'csv_file_path', a jedna ma' attachment_file_path'.Z tego powodu mam wrażenie, że w wielu przypadkach najlepiej będzie pasował do twoich potrzeb najlepiej, aby użyć klasy dummy, aby przetestować zachowanie modułu, jak w odpowiedzi @Martijna: – nzifnab

+1

@ nzifnab, aby było jasne, moduł nie dodaje metody, klasa dziecko jest jawnie. Niezależnie od tego, czy wspólne przykłady są tutaj odpowiednie, to wywołanie oceny właściwe dla podstawy kodu. Jednak nadal można z nich korzystać w ten sposób. Możliwe jest przekazywanie im informacji, tak jak w rozmowie: 'it_behaves_like 'działa jak upload',: csv' –

6

Można po prostu przetestować moduł niejawnie, pozostawiając testy w klasach, które zawierają ten moduł. Ewentualnie możesz dołączyć inne moduły wymagane do swojej sztucznej klasy. Na przykład metody validates w modelach AR są dostarczane przez ActiveModel::Validations. Tak więc, dla testów:

class DummyClass 
    include ActiveModel::Validations 
    include HasTerms 
end 

Mogą być też inne moduły trzeba wnieść w oparciu o zależności niejawnie polegać na w module HasTerms.

+0

Zgadzam się, że testowanie niejawna jest łatwe, ale czuję, że muszą być w stanie również do testowania rzeczy. Jest to szczególnie istotne, jeśli chodzi o pisanie funkcji klas, których żadna z moich klas jeszcze nie używa. – Bryce

+0

Coś jak "DummyClass" jest alternatywą, której szukasz. – rossta

5

miałem problemy z tym sobie i wyczarował następujące rozwiązanie, które jest bardzo podobny pomysł rossta, ale wykorzystuje anonimową klasę zamiast:

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

To jedyne rozwiązanie, które udało mi się znaleźć. Kiedy idziesz z nazwaną klasą manekina, twoja specyfikacja zaczyna krwawić, jeśli wywoła się metodę modyfikującą klasę, wtedy następna specyfikacja zobaczy także modyfikację klasy. Chociaż moje jest bardziej jak 'let (: dummy_class) {Class.new (...)}' w przeciwieństwie do wstawiania go bezpośrednio do bloku 'it'. – nzifnab

2

Opierając się na doskonałej odpowiedzi Aaron K. here, istnieje kilka fajnych sztuczek, których można używać z described_class, które RSpec zapewnia, że ​​twoje metody są wszechobecne i sprawiają, że fabryki działają dla ciebie. Oto fragment wspólnej przykład ja niedawno do wniosku:

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

Oto kolejny przykład (przy użyciu Factorygirl na "tworzenie" metoda”i shared_examples_for)

dotyczą specyfikacji

#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 

commentable koncern

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 

i restraunt_spec mogą wyglądać mniej więcej tak (jestem n ot rspec guru, więc nie sądzę, że mój sposób pisania specyfikacji jest dobre - najważniejszą rzeczą jest na początku):

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