Skip to content

Commit a9a2bf2

Browse files
committed
Expanded trait error message to include list of defined traits.
- Files changed: - lib/factory_bot/definition.rb - spec/acceptance/traits_spec.rb - spec/acceptance/enum_traits_spec.rb - Tests added: - added new use-case tests for: - no registered traits - single registered trait - multiple registered traits - multiple registered traits through multiple inheritance
1 parent 5df1196 commit a9a2bf2

File tree

3 files changed

+256
-28
lines changed

3 files changed

+256
-28
lines changed

lib/factory_bot/definition.rb

+28-25
Original file line numberDiff line numberDiff line change
@@ -125,31 +125,8 @@ def callback(*names, &block)
125125

126126
def base_traits
127127
@base_traits.map { |name| trait_by_name(name) }
128-
rescue KeyError => error
129-
raise error_with_definition_name(error)
130-
end
131-
132-
# detailed_message introduced in Ruby 3.2 for cleaner integration with
133-
# did_you_mean. See https://bugs.ruby-lang.org/issues/18564
134-
if KeyError.method_defined?(:detailed_message)
135-
def error_with_definition_name(error)
136-
message = error.message + " referenced within \"#{name}\" definition"
137-
138-
error.class.new(message, key: error.key, receiver: error.receiver)
139-
.tap { |new_error| new_error.set_backtrace(error.backtrace) }
140-
end
141-
else
142-
def error_with_definition_name(error)
143-
message = error.message
144-
message.insert(
145-
message.index("\nDid you mean?") || message.length,
146-
" referenced within \"#{name}\" definition"
147-
)
148-
149-
error.class.new(message).tap do |new_error|
150-
new_error.set_backtrace(error.backtrace)
151-
end
152-
end
128+
rescue KeyError => key_error
129+
raise error_with_definition_name key_error
153130
end
154131

155132
def additional_traits
@@ -158,6 +135,8 @@ def additional_traits
158135

159136
def trait_by_name(name)
160137
trait_for(name) || Internal.trait_by_name(name, klass)
138+
rescue KeyError => key_error
139+
raise error_with_defined_traits key_error
161140
end
162141

163142
def trait_for(name)
@@ -205,5 +184,29 @@ def automatically_register_defined_enums?(klass)
205184
FactoryBot.automatically_define_enum_traits &&
206185
klass.respond_to?(:defined_enums)
207186
end
187+
188+
def error_with_definition_name(key_error)
189+
message = key_error.message + ". Referenced within \"#{name}\" definition"
190+
new_trait_error(message, key_error)
191+
end
192+
193+
def error_with_defined_traits(key_error)
194+
trait_names = defined_traits_names.sort.map { |n| ":#{n}" }.join(", ")
195+
message = "#{key_error.message}. Registered traits: [#{trait_names}]"
196+
197+
new_trait_error(message, key_error)
198+
end
199+
200+
def new_trait_error(message, key_error)
201+
# detailed_message introduced in Ruby 3.2 for cleaner integration with
202+
# did_you_mean. See https://bugs.ruby-lang.org/issues/18564
203+
if KeyError.method_defined?(:detailed_message)
204+
KeyError.new(message, key: key_error.key, receiver: key_error.receiver)
205+
else
206+
KeyError.new(message)
207+
end.tap do |error|
208+
error.set_backtrace(key_error.backtrace)
209+
end
210+
end
208211
end
209212
end

spec/acceptance/enum_traits_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def each(&block)
132132

133133
Task.statuses.each_key do |trait_name|
134134
expect { FactoryBot.build(:task, trait_name) }.to raise_error(
135-
KeyError, "Trait not registered: \"#{trait_name}\""
135+
KeyError, /Trait not registered: "#{trait_name}"/
136136
)
137137
end
138138

spec/acceptance/traits_spec.rb

+227-2
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,15 @@ def build_user_factory_with_admin_trait(trait_name)
333333
end
334334
end
335335

336+
expected_message = [
337+
"Trait not registered: \"inaccessible_trait\".",
338+
"Registered traits: [].",
339+
"Referenced within \"user\" definition"
340+
].join(" ")
341+
336342
expect { FactoryBot.build(:user) }.to raise_error(
337343
KeyError,
338-
'Trait not registered: "inaccessible_trait" referenced within "user" definition'
344+
expected_message
339345
)
340346
end
341347

@@ -370,14 +376,233 @@ def build_user_factory_with_admin_trait(trait_name)
370376
end
371377
end
372378

379+
expected_message = [
380+
"Trait not registered: \"inaccessible_trait\".",
381+
"Registered traits: [:admin].",
382+
"Referenced within \"admin\" definition"
383+
].join(" ")
384+
373385
expect { FactoryBot.build(:user, :admin) }.to raise_error(
374386
KeyError,
375-
'Trait not registered: "inaccessible_trait" referenced within "admin" definition'
387+
expected_message
376388
)
377389
end
378390
end
379391
end
380392

393+
describe "trait exception error messages" do
394+
before { define_model("User", name: :string) }
395+
396+
context "when a missing trait is defined within the factory" do
397+
it "includes the definition name where the missing trait was called" do
398+
FactoryBot.define do
399+
factory :user do
400+
inaccessible_trait
401+
end
402+
end
403+
404+
expect { FactoryBot.build(:user) }.to raise_error(
405+
KeyError,
406+
/Referenced within "user" definition/
407+
)
408+
end
409+
end
410+
411+
context "when a missing trait is called by the user" do
412+
it "excludes the factory definition name" do
413+
FactoryBot.define do
414+
factory :user
415+
end
416+
417+
expect { FactoryBot.build(:user, :missing_trait) }.to raise_error(KeyError) do |error|
418+
expect(error.message).not_to include "Referenced within \"user\" definition"
419+
end
420+
end
421+
end
422+
423+
context "when no defined traits" do
424+
context "includes an empty list" do
425+
it "when the factory references a missing trait internally" do
426+
FactoryBot.define do
427+
factory :user do
428+
inaccessible_trait
429+
end
430+
end
431+
432+
expect { FactoryBot.build(:user) }.to raise_error(
433+
KeyError,
434+
/Registered traits: \[\]/
435+
)
436+
end
437+
438+
it "when a missing trait is called by the user" do
439+
FactoryBot.define do
440+
factory :user
441+
end
442+
443+
expect { FactoryBot.build(:user, :missing_trait) }.to raise_error(
444+
KeyError,
445+
"Trait not registered: \"missing_trait\". Registered traits: []"
446+
)
447+
end
448+
end
449+
end
450+
451+
context "with a single defined trait" do
452+
context "includes a list if the single trait" do
453+
it "when the factory references a missing trait internally" do
454+
FactoryBot.define do
455+
factory :user do
456+
trait :trait_1
457+
458+
inaccessible_trait
459+
end
460+
end
461+
462+
expected_message = [
463+
"Trait not registered: \"inaccessible_trait\".",
464+
"Registered traits: [:trait_1].",
465+
"Referenced within \"user\" definition"
466+
].join(" ")
467+
468+
expect { FactoryBot.build(:user) }.to raise_error(
469+
KeyError,
470+
expected_message
471+
)
472+
end
473+
474+
it "when a missing trait is called by the user" do
475+
FactoryBot.define do
476+
factory :user do
477+
trait :trait_1
478+
end
479+
end
480+
481+
expect { FactoryBot.build(:user, :missing_trait) }.to raise_error(
482+
KeyError,
483+
"Trait not registered: \"missing_trait\". Registered traits: [:trait_1]"
484+
)
485+
end
486+
end
487+
end
488+
489+
context "with multiple defined traits" do
490+
context "includes a list of all traits" do
491+
it "when the factory references a missing trait internally" do
492+
FactoryBot.define do
493+
factory :user do
494+
trait :trait_1
495+
trait :trait_2
496+
trait :trait_3
497+
trait :trait_4
498+
499+
inaccessible_trait
500+
end
501+
end
502+
503+
expected_message = [
504+
"Trait not registered: \"inaccessible_trait\".",
505+
"Registered traits: [:trait_1, :trait_2, :trait_3, :trait_4].",
506+
"Referenced within \"user\" definition"
507+
].join(" ")
508+
509+
expect { FactoryBot.build(:user) }.to raise_error(
510+
KeyError,
511+
expected_message
512+
)
513+
end
514+
515+
it "when a missing trait is called by the user" do
516+
FactoryBot.define do
517+
factory :user do
518+
trait :trait_1
519+
trait :trait_2
520+
trait :trait_3
521+
trait :trait_4
522+
end
523+
end
524+
525+
expected_message = [
526+
"Trait not registered: \"missing_trait\".",
527+
"Registered traits: [:trait_1, :trait_2, :trait_3, :trait_4]"
528+
].join(" ")
529+
530+
expect { FactoryBot.build(:user, :missing_trait) }.to raise_error(
531+
KeyError,
532+
expected_message
533+
)
534+
end
535+
end
536+
end
537+
538+
context "with both defined and inherited traits" do
539+
context "includes a list of all traits" do
540+
it "when the factory references a missing trait internally" do
541+
FactoryBot.define do
542+
factory :user do
543+
trait :user_trait_1
544+
trait :user_trait_2
545+
546+
factory :admin do
547+
trait :admin_trait_1
548+
trait :admin_trait_2
549+
550+
factory :dev do
551+
trait :dev_trait_1
552+
trait :dev_trait_2
553+
554+
inaccessible_trait
555+
end
556+
end
557+
end
558+
end
559+
560+
expected_message = [
561+
"Trait not registered: \"inaccessible_trait\".",
562+
"Registered traits: [:admin_trait_1, :admin_trait_2,",
563+
":dev_trait_1, :dev_trait_2, :user_trait_1, :user_trait_2].",
564+
"Referenced within \"dev\" definition"
565+
].join(" ")
566+
567+
expect { FactoryBot.build(:dev) }.to raise_error(
568+
KeyError,
569+
expected_message
570+
)
571+
end
572+
573+
it "when a missing trait is called by the user" do
574+
FactoryBot.define do
575+
factory :user do
576+
trait :user_trait_1
577+
trait :user_trait_2
578+
579+
factory :admin do
580+
trait :admin_trait_1
581+
trait :admin_trait_2
582+
583+
factory :dev do
584+
trait :dev_trait_1
585+
trait :dev_trait_2
586+
end
587+
end
588+
end
589+
end
590+
591+
expected_message = [
592+
"Trait not registered: \"missing_trait\".",
593+
"Registered traits: [:admin_trait_1, :admin_trait_2,",
594+
":dev_trait_1, :dev_trait_2, :user_trait_1, :user_trait_2]"
595+
].join(" ")
596+
597+
expect { FactoryBot.build(:dev, :missing_trait) }.to raise_error(
598+
KeyError,
599+
expected_message
600+
)
601+
end
602+
end
603+
end
604+
end
605+
381606
describe "traits with callbacks" do
382607
before do
383608
define_model("User", name: :string)

0 commit comments

Comments
 (0)