RSpec: Factory definition for associated object
Asked Answered
L

3

7

I have the following three models: Product, Warehouse and Inventory

# app/models/product.rb
class Product < ApplicationRecord
  has_many :inventories
  has_many :warehouses, through: :inventories  
end

# app/models/warehouse.rb
class Warehouse < ApplicationRecord
  has_many :inventories
  has_many :products, through: :inventories
end

# app/models/inventory.rb
class Inventory < ApplicationRecord
  belongs_to :product
  belongs_to :warehouse
end

I have this factory for Inventory:

FactoryBot.define do
  factory :inventory do
    product { nil }
    warehouse { nil }
    item_count { 1 }
    low_item_threshold { 1 }
  end
end

How can I use this factory for Inventory or what changes are needed in my other factories so that I can have a spec something like this?

RSpec.describe Inventory, type: :model do
  it "has a valid factory" do
   expect(FactoryBot.build(:inventory)).to be_valid
  end
end
Leahleahey answered 24/11, 2019 at 21:13 Comment(0)
H
6

What you need is to change the :inventory factory definition, like this

FactoryBot.define do
  factory :inventory do
    product
    warehouse
    item_count { 1 }
    low_item_threshold { 1 }
  end
end

This will "tell" factory bot to instantiate the associated objects (https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#associations)

But for this to work, you need to define warehouse and product factories.

Hydraulic answered 9/12, 2019 at 12:29 Comment(0)
D
0

You can use either the create or build methods passing the name if the factory as a symbol:

  it "has a valid factory" do
   expect(create(:inventory)).to be_valid
 end

  # OR

  it "has a valid factory" do
   expect(build(:inventory)).to be_valid
 end

create will save the model while build will simply instantiate it. If you are having trouble getting your factories loaded, ensure they are in the right place.

Depressant answered 24/11, 2019 at 22:9 Comment(0)
K
0

you might change definition of the class Inventory to:

# app/models/inventory.rb
class Inventory < ApplicationRecord
  belongs_to :product, optional: true
  belongs_to :warehouse, optional: true 
end

and you will get successful validation

inventory = FactoryBot.build(:inventory)
inventory.valid? #true

###############################################

Explanation:

to build valid Inventory object with current definition(like in question description) of the model its necessary to initialize associated objects also. So every time validation checks if warehouse and product attributes present. But its possible to avoid such behaviour with associations attribute optional: true.

# app/models/inventory.rb
class Inventory < ApplicationRecord
  belongs_to :product
  belongs_to :warehouse
end

FactoryBot.define do
  factory :inventory do
    product { nil }
    warehouse { nil }
    item_count { 1 }
    low_item_threshold { 1 }
  end
end

inventory = FactoryBot.build(:inventory)
inventory.valid? #false 
inventory.errors.full_messages # ["Product must exist", "Warehouse must exist"] 

:required When set to true, the association will also have its presence validated. This will validate the association itself, not the id. You can use :inverse_of to avoid an extra query during validation. NOTE: required is set to true by default and is deprecated. If you don’t want to have association presence validated, use optional: true. https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to

Karaganda answered 10/6, 2021 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.