rspec + factory model testing nested attributes

最后都变了- 提交于 2019-12-30 11:53:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!