1
0
Fork 0
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:
Daniel Colson 2020-07-10 11:53:20 -04:00 committed by GitHub
parent 9879060289
commit d05a9a3c4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 19 deletions

View file

@ -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

View file

@ -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