diff --git a/lib/factory_bot/definition.rb b/lib/factory_bot/definition.rb index 26ed835..4aded74 100644 --- a/lib/factory_bot/definition.rb +++ b/lib/factory_bot/definition.rb @@ -111,6 +111,20 @@ module FactoryBot def base_traits @base_traits.map { |name| trait_by_name(name) } + rescue KeyError => error + raise error_with_definition_name(error) + end + + def error_with_definition_name(error) + message = error.message + message.insert( + message.index("\nDid you mean?") || message.length, + " referenced within \"#{name}\" definition" + ) + + error.class.new(message).tap do |new_error| + new_error.set_backtrace(error.backtrace) + end end def additional_traits diff --git a/spec/acceptance/traits_spec.rb b/spec/acceptance/traits_spec.rb index 3f1cfba..293f963 100644 --- a/spec/acceptance/traits_spec.rb +++ b/spec/acceptance/traits_spec.rb @@ -9,10 +9,6 @@ describe "an instance generated by a factory with multiple traits" do great: :string) FactoryBot.define do - factory :user_without_admin_scoping, class: User do - admin_trait - end - factory :user do name { "John" } @@ -187,15 +183,6 @@ describe "an instance generated by a factory with multiple traits" do its(:date_of_birth) { should eq Date.parse("1/1/2000") } end - context "factory outside of scope" do - subject { FactoryBot.create(:user_without_admin_scoping) } - - it "raises an error" do - expect { subject } - .to raise_error(KeyError, "Trait not registered: \"admin_trait\"") - end - end - context "child factory using grandparents' trait" do subject { FactoryBot.create(:female_great_user) } its(:great) { should eq "GREAT!!!" } @@ -293,15 +280,81 @@ describe "trait indifferent access" do end describe "looking up traits that don't exist" do - it "raises a KeyError" do - define_class("User") + context "when passing an invalid override trait" do + it "raises a KeyError" do + define_class("User") - FactoryBot.define do - factory :user + FactoryBot.define do + factory :user + end + + expect { FactoryBot.build(:user, double("not a trait")) } + .to raise_error(KeyError) + end + end + + context "when the factory includes an invalid default trait" do + it "raises a KeyError including the factory name" do + define_class("User") + + FactoryBot.define do + factory :user do + inaccessible_trait + end + + factory :some_other_factory do + trait :inaccessible_trait + end + end + + expect { FactoryBot.build(:user) }.to raise_error( + KeyError, + 'Trait not registered: "inaccessible_trait" referenced within "user" definition' + ) end - expect { FactoryBot.build(:user, double("not a trait")) } - .to raise_error(KeyError) + it "maintains 'Did you mean?' suggestions at the end of the error message" do + define_class("User") + + FactoryBot.define do + trait :not_quit + + factory :user do + not_quite + end + end + + expect { FactoryBot.build(:user) }.to raise_error( + KeyError, + <<~MSG.strip + Trait not registered: "not_quite" referenced within "user" definition + Did you mean? "not_quit" + MSG + ) + end + end + + context "when a trait includes an invalid default trait" do + it "raises a KeyError including the factory name" do + define_class("User") + + FactoryBot.define do + factory :user do + trait :admin do + inaccessible_trait + end + end + + factory :some_other_factory do + trait :inaccessible_trait + end + end + + expect { FactoryBot.build(:user, :admin) }.to raise_error( + KeyError, + 'Trait not registered: "inaccessible_trait" referenced within "admin" definition' + ) + end end end