2016-04-07 4 views
0

У меня нет большого опыта тестирования в Rails, поэтому я не совсем понимаю, как работают рамки проверки под капотом.Ошибка проверки атрибутов модели Rails, которые могут зависеть от метода

Ошибка я получаю:

Failure/Error: self.slug ||= name.parameterize 
NoMethodError: 
undefined method `parameterize' for nil:NilClass 

Эта ошибка возникает даже при создании новой организации, со всеми атрибутами, используя FactroyGirl (или по старинке с Organization.new(...).save).

Я понимаю, почему происходит ошибка, я не понимаю, как name оценивается как nil и, следовательно, как написать тест таким образом, чтобы он работал правильно.

Я проверил, что организация и ее атрибуты существуют в пределах области тестирования с использованием таких утверждений, как puts "short_name: #{org.short_name}".

require 'rails_helper' 

class Organization < ActiveRecord::Base 

[....] 

validates :name, :slug, uniqueness: { case_sensitive: false } 
    validates :name, :short_name, :slug, presence: true 
    validates :name, length: { maximum: 255 } 
    validates :short_name, length: { maximum: 20 } 
    validates :slug, length: { maximum: 200 } 

before_validation :generate_slug 
    def generate_slug 
    self.slug ||= name.parameterize 
    end 

    before_validation :generate_short_name 
    def generate_short_name 
    self.short_name ||= begin 
     if name? 
     if name.size > 20 
      name.split(/[ -]/).first.first(20) 
     else 
      name 
     end 
     end 
    end 
    end 
end 

Схема:

create_table "organizations", force: :cascade do |t| 
    t.integer "organization_type_id", limit: 4 
    t.string "name",     limit: 255 
    t.string "short_name",    limit: 20 
    t.string "slug",     limit: 200 
    t.text  "description",   limit: 65535 
    t.integer "year_founded",   limit: 2, unsigned: true 
    t.datetime "last_published_date" 
    t.date  "notification_sent_date" 
    t.datetime "last_imported_at" 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
    end 

Организация завод:

FactoryGirl.define do 
    factory :organization, class: Organization do 
    name 'Example & Co' 

    trait :all_fields do 
     slug 'example-co' 
     short_name 'Example & Co' 
     description ‘This is a description.’ 
     year_founded 2010 
    end 
    end 
end 

Все следующие тесты проверки работает над другой организацией атрибутов. Предполагается, что метод generate_slug должен создать безопасный слиг url из атрибута name, если пользовательский пул не предоставлен. ПРИМЕЧАНИЕ. Я не являюсь автором кода, я только строю тестовый набор для приложения, созданного подрядчиками.

Тесты:

Это первый тест проходит, я включил его для информации/проверки

RSpec.describe Organization, :type => :model do 

    #validating my factory:  
    describe 'FactoryGirl' do 
    it 'factory generating all fields should be valid' do 
     create(:organization, :all_fields).should be_valid 
     build(:organization, :all_fields).should_not be_valid 
    end 

    it 'factory generating name field should be valid' do 
     create(:organization).should be_valid 
     build(:organization).should_not be_valid 
    end 
    end 

Испытания, которые erroring из:

describe 'name' do 
    let(:org) {FactoryGirl.build(:organization, :all_fields)} 
    context 'is valid' do 
    # this is the only test on #name that fails 
    it { should validate_presence_of(:name) } 
    end 
end 

describe 'slug' do 
    let(:org) {FactoryGirl.build(:organization, :all_fields)} 
    context 'is valid' do 
    # this is the only test on #slug that fails,  
    it { should validate_presence_of(:slug) } 
    end 
end 

describe 'short_name' do 
    # All of the tests on short_name fail 
    let(:org) {FactoryGirl.build(:organization, :all_fields)} 
    context 'is valid' do 
    it { should validate_presence_of(:short_name) } 
    it { should have_valid(:short_name).when(org.short_name, 'Example & Co') } 
    it { should validate_length_of(:short_name).is_at_most(20) }  
    end 

    context 'is not valid' do 
    it { should_not have_valid(:short_name).when('a' * 21) }  
    end 
end 
+0

Я вижу, что вы создали тест для проверки правильности своей фабрики. К счастью, в «FactoryGirl» есть опция автоматически. https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#linting-factories –

ответ

1

Первое:

Вы должны установить правильный предмет в своих тестах. Это объект, к которому будут относиться все однонаправленные it. Вы сделали создать let, но так как вы никогда явно установить его в качестве предмета, тест взял неявный объект (который по умолчанию Organization.new в вашем случае)

Чтобы установить явную тему вы можете написать:

describe 'name' do 
    subject { FactoryGirl.build(:organization, :all_fields) } 

    it { should validate_presence_of(:name) } 
    # or with the new syntax 
    it { is_expected.to validate_presence_of(:name) } 
end 

вы можете прочитать больше о неявной против явного субъекта и острот здесь: http://www.relishapp.com/rspec/rspec-core/v/3-4/docs/subject/one-liner-syntax

Второй:

другой вопрос заключается в том, что, если вы используете shoulda-matchers, он установит name на номер nil, чтобы увидеть, что, когда атрибута нет, должна произойти ошибка проверки.

Но когда имя установлено на nil, обратный вызов before_validation вызывает ошибку, так как он всегда должен найти name.

Вы можете изменить функцию обратного вызова, как это (example in rails documentation):

before_validation :generate_slug 
def generate_slug 
    self.slug ||= name.parameterize if attribute_present?("name") 
end 

Третье:

предложение. Если у вас установлены фабрики, и у вас есть драгоценный камень, то вы можете написать довольно сжатые спецификации. Например, вот так.

RSpec.describe Organization, :type => :model do 
    # If you fix the callback you don't even need 
    # to set explicit subject here 

    it { is_expected.to validate_presence_of(:name) } 
    it { is_expected.to validate_lenght_of(:name).is_at_most(255) } 
    ... etc 

    # Add custom contexts only for the before_validation callbacks, 
    # because shoulda-matchers cannot test them. 
    # One possible way (there are different ways and opinions on how 
    # should implement this kind of test): 
    describe '#slug' do 
    let(:organization) { described_class.new(name: 'Ab cd', slug: slug) } 

    before { organization.valid? } 

    subject { organization.slug } 

    context 'when it is missing' do 
     let(:slug) { nil } 
     let(:result) { 'ab_cd' } 

     it 'gets created' do 
     expect(subject).to eq(result) 
     end 
    end 

    context 'when it is not missing' do 
     let(:slug) { 'whatever' } 

     it "won't change" do 
     expect(subject).to eq(slug) 
     end 
    end 
    end 
end 

Для получения дополнительных примеров вы можете просмотреть the shoulda-matchers documentation.

+0

Хороший материал! Спасибо! Мне нравятся любые ссылки, которые могут объяснить, как структурированы тестовые структуры. Например, я видел ссылки на «тему» ​​в документации по gem, но ничего не объяснял, что это такое или как его использовать. Cheers! – NickTerrafranca

+0

Можете ли вы уточнить комментарий: «# добавить пользовательские контексты только для обратных вызовов before_validation»? – NickTerrafranca

+0

Я обновил пример в части обратного вызова before_validation. Так я сам создавал бы что-то подобное, но есть разные подходы (например, всегда в тестировании ..). Надеюсь, что это поможет! –