describe "FactoryBot.lint" do it "raises when a factory is invalid" do define_model "User", name: :string do validates :name, presence: true end define_model "AlwaysValid" FactoryBot.define do factory :user do factory :admin_user end factory :always_valid end error_message = <<~ERROR_MESSAGE.strip The following factories are invalid: * user - Validation failed: Name can't be blank (ActiveRecord::RecordInvalid) * admin_user - Validation failed: Name can't be blank (ActiveRecord::RecordInvalid) ERROR_MESSAGE expect { FactoryBot.lint }.to raise_error FactoryBot::InvalidFactoryError, error_message end it "does not raise when all factories are valid" do define_model "User", name: :string do validates :name, presence: true end FactoryBot.define do factory :user do name { "assigned" } end end expect { FactoryBot.lint }.not_to raise_error end it "allows for selective linting" do define_model "InvalidThing", name: :string do validates :name, presence: true end define_model "ValidThing", name: :string FactoryBot.define do factory :valid_thing factory :invalid_thing end expect { only_valid_factories = FactoryBot.factories.reject { |factory| factory.name =~ /invalid/ } FactoryBot.lint only_valid_factories }.not_to raise_error end describe "trait validation" do context "enabled" do it "raises if a trait produces an invalid object" do define_model "User", name: :string do validates :name, presence: true end FactoryBot.define do factory :user do name { "Yep" } trait :unnamed do name { nil } end end end error_message = <<~ERROR_MESSAGE.strip The following factories are invalid: * user+unnamed - Validation failed: Name can't be blank (ActiveRecord::RecordInvalid) ERROR_MESSAGE expect { FactoryBot.lint traits: true }.to raise_error FactoryBot::InvalidFactoryError, error_message end it "does not raise if a trait produces a valid object" do define_model "User", name: :string do validates :name, presence: true end FactoryBot.define do factory :user do name { "Yep" } trait :renamed do name { "Yessir" } end end end expect { FactoryBot.lint traits: true }.not_to raise_error end end context "disabled" do it "does not raises if a trait produces an invalid object" do define_model "User", name: :string do validates :name, presence: true end FactoryBot.define do factory :user do name { "Yep" } trait :unnamed do name { nil } end end end expect { FactoryBot.lint traits: false FactoryBot.lint }.not_to raise_error end end end describe "factory strategy for linting" do it "uses the requested strategy" do define_class "User" do attr_accessor :name def save! raise "expected :build strategy, #save! shouldn't be invoked" end end FactoryBot.define do factory :user do name { "Barbara" } end end expect { FactoryBot.lint strategy: :build }.not_to raise_error end it "uses the requested strategy during trait validation" do define_class "User" do attr_accessor :name def save! raise "expected :build strategy, #save! shouldn't be invoked" end end FactoryBot.define do factory :user do name { "Barbara" } trait :male do name { "Bob" } end end end expect { FactoryBot.lint traits: true, strategy: :build }.not_to raise_error end end describe "verbose linting" do it "prints the backtrace for each factory error" do define_class("InvalidThing") do def save! raise "invalid" end end FactoryBot.define do factory :invalid_thing end expect { FactoryBot.lint(verbose: true) }.to raise_error( FactoryBot::InvalidFactoryError, %r{#{__FILE__}:\d*:in `save!'} ) end end end