验证可能依赖于方法的 Rails 模型属性时出错
Error validating Rails model attributes that can depend on a method
我在 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
以下所有验证测试都适用于其他组织属性。
如果用户未提供 slug,generate_slug 方法应该从 name 属性创建一个 url 安全 slug。
注意:我不是代码的作者,我只是为承包商构建的应用构建测试套件。
测试:
第一个测试通过了,我把它包含在 information/verification
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
出错的测试:
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
第一个:
您需要在测试中设置正确的主题。这是所有 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
gem,它会将 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
第三名:
一个建议。如果你设置了工厂并且你有 shoulda-matchers gem,你可以写出非常简洁的规范。比如像这样。
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。
我在 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
以下所有验证测试都适用于其他组织属性。 如果用户未提供 slug,generate_slug 方法应该从 name 属性创建一个 url 安全 slug。 注意:我不是代码的作者,我只是为承包商构建的应用构建测试套件。
测试:
第一个测试通过了,我把它包含在 information/verification
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
出错的测试:
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
第一个:
您需要在测试中设置正确的主题。这是所有 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
gem,它会将 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
第三名:
一个建议。如果你设置了工厂并且你有 shoulda-matchers gem,你可以写出非常简洁的规范。比如像这样。
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。