mirror of
https://github.com/thoughtbot/factory_bot.git
synced 2022-11-09 11:43:51 -05:00
Add definition names to default trait key errors (#1421)
* Add definition names to default trait key errors Closes #1222 Before this commit, referencing a trait that didn't exist would raise a generic `KeyError: Trait not registered: "trait_name"`. This can sometime make it difficult to know where exactly the error is coming from. The fact that implicitly declared associations and sequences will fall back to implicit traits if they can't be found compounds this problem. If various associations, sequences, or traits share the same name, the hunt for the error can take some time. With this commit we include the factory or trait name (i.e. the definition name) in the error message to help identify where the problematic trait originated. Because trait lookup relies on a more generic class that raises a `KeyError` for missing keys, we rescue that error, then construct a new error with our custom message using the original error's message and backtrace.
This commit is contained in:
parent
9879060289
commit
d05a9a3c4c
2 changed files with 86 additions and 19 deletions
|
@ -111,6 +111,20 @@ module FactoryBot
|
||||||
|
|
||||||
def base_traits
|
def base_traits
|
||||||
@base_traits.map { |name| trait_by_name(name) }
|
@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
|
end
|
||||||
|
|
||||||
def additional_traits
|
def additional_traits
|
||||||
|
|
|
@ -9,10 +9,6 @@ describe "an instance generated by a factory with multiple traits" do
|
||||||
great: :string)
|
great: :string)
|
||||||
|
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :user_without_admin_scoping, class: User do
|
|
||||||
admin_trait
|
|
||||||
end
|
|
||||||
|
|
||||||
factory :user do
|
factory :user do
|
||||||
name { "John" }
|
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") }
|
its(:date_of_birth) { should eq Date.parse("1/1/2000") }
|
||||||
end
|
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
|
context "child factory using grandparents' trait" do
|
||||||
subject { FactoryBot.create(:female_great_user) }
|
subject { FactoryBot.create(:female_great_user) }
|
||||||
its(:great) { should eq "GREAT!!!" }
|
its(:great) { should eq "GREAT!!!" }
|
||||||
|
@ -293,6 +280,7 @@ describe "trait indifferent access" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "looking up traits that don't exist" do
|
describe "looking up traits that don't exist" do
|
||||||
|
context "when passing an invalid override trait" do
|
||||||
it "raises a KeyError" do
|
it "raises a KeyError" do
|
||||||
define_class("User")
|
define_class("User")
|
||||||
|
|
||||||
|
@ -303,6 +291,71 @@ describe "looking up traits that don't exist" do
|
||||||
expect { FactoryBot.build(:user, double("not a trait")) }
|
expect { FactoryBot.build(:user, double("not a trait")) }
|
||||||
.to raise_error(KeyError)
|
.to raise_error(KeyError)
|
||||||
end
|
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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
describe "traits with callbacks" do
|
describe "traits with callbacks" do
|
||||||
|
|
Loading…
Reference in a new issue