问题
I have a rails4 app with rspec + factory_girl. I would like to test the validation that makes sure the product has at least one feature, competition, usecase and industry. The first 3 must belong to a product, but industry can exist on its own. I haven't tried to put industry in the test yet since I can't even make work the first 3.
I tried the approach below in which I create a product factory that has product_feature, product_competition and product_usecase. For some reason it's not working.
Do I use the right approach? If so, what's wrong with my code?
1) Product nested attribute validation has a valid factory
Failure/Error: expect(create(:product_with_nested_attrs)).to be_valid
ActiveRecord::RecordInvalid:
Validation failed: You have to choose at least 1 industry., You must have at least 1 product feature., You must name at least 1 competition., You must describe at least 1 usecase.
product.rb (UPDATED)
belongs_to :user
has_many :industry_products, dependent: :destroy, inverse_of: :product
has_many :industries, through: :industry_products #industry exists without product; connected with has_many thru association
has_many :product_features, dependent: :destroy
has_many :product_competitions, dependent: :destroy
has_many :product_usecases, dependent: :destroy
accepts_nested_attributes_for :industry_products, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :product_features, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :product_competitions, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :product_usecases, reject_if: :all_blank, allow_destroy: true
#UPDATE
validate :product_features_limit #Also have it for product_usecases and product_competititons
def product_features_limit
if self.product_features.reject(&:marked_for_destruction?).count > 10
self.errors.add :base, "You can't have more than 10 features."
elsif self.product_features.reject(&:marked_for_destruction?).count < 1
self.errors.add :base, "You must have at least 1 product feature."
end
end
factory
FactoryGirl.define do
factory :product_competititon do
competitor { Faker::Commerce.product_name }
differentiator { Faker::Lorem.paragraph }
product
end
factory :product_feature do
feature { Faker::Lorem.paragraph }
product
end
factory :product_usecase do
example { Faker::Lorem.sentence }
detail { Fakert::Lorem.paragraph }
product
end
factory :product do
name { Faker::Commerce.product_name }
company { Faker::Company.name }
website { 'https://example.com' }
oneliner { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
user
factory :product_with_nested_attrs do
transient do
nested_attrs_count 1
end
after(:create) do |product, evaluator|
create_list(:product_feature, evaluator.nested_attrs_count, product: product)
create_list(:product_competititon, evaluator.nested_attrs_count, product: product)
create_list(:product_usecase, evaluator.nested_attrs_count, product: product)
end
end
end
end
product_spec.rb
RSpec.describe Product, type: :model do
describe "nested attribute validation" do
it "has a valid factory" do
expect(create(:product_with_nested_attrs).to be_valid
end
end
end
回答1:
There is a gem shoulda
(https://github.com/thoughtbot/shoulda) which let you test accepts_nested_attributes_for
(validations and associations as well) directly with one single matcher. But if you prefer to test the behavior(what) rather then implementation(how), you can do something like follows ...
Just as what I mentioned before (setting up objects for model testing with factory_girl), I would remove associations from factories first.
Factories
factory :product_competititon do
competitor { Faker::Commerce.product_name }
differentiator { Faker::Lorem.paragraph }
end
factory :product_feature do
feature { Faker::Lorem.paragraph }
end
factory :product_usecase do
example { Faker::Lorem.sentence }
detail { Fakert::Lorem.paragraph }
end
factory :product do
name { Faker::Commerce.product_name }
company { Faker::Company.name }
website { 'https://example.com' }
oneliner { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
end
Specs
RSpec.describe Product, type: :model do
describe "validation" do
let(:user) { create(:user) }
it "should be valid if a product has at least one competition, feature, usecase and industry" do
attr = attributes_for(:project).merge({
user_id: user.id,
product_competitions: [attributes_for(:project_competition)],
product_features: [attributes_for(:project_feature)],
product_usecases: [attributes_for(:project_usecase)],
product_industries: [attributes_for(:industry)],
})
expect(Product.new(attr)).to be_valid
end
it "should be invalid if no competition is given" do
attr = attributes_for(:project).merge({
user_id: user.id,
product_features: [attributes_for(:project_feature)],
product_usecases: [attributes_for(:project_usecase)],
product_industries: [attributes_for(:industry)],
})
expect(Product.new(attr)).to be_invalid
end
it "should be invalid if no feature is given" do
attr = attributes_for(:project).merge({
user_id: user.id,
product_competitions: [attributes_for(:project_competition)],
product_usecases: [attributes_for(:project_usecase)],
product_industries: [attributes_for(:industry)],
})
expect(Product.new(attr)).to be_invalid
end
# ... similar test cases for usecase and industry validation ...
end
end
来源:https://stackoverflow.com/questions/36569195/rspec-factory-model-testing-nested-attributes