Skip to content

Commit 9dae7af

Browse files
committed
Fixes scenario where recursive depth = 0 not generating properties on root object
1 parent e7e8518 commit 9dae7af

File tree

4 files changed

+64
-4
lines changed

4 files changed

+64
-4
lines changed

src/AutoFakerBinder.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,30 @@ private static bool ShouldSkip(AutoMember autoMember, AutoFakerContext context)
179179
if (config.TreeDepth != null && stackCount >= config.TreeDepth)
180180
return true;
181181

182-
// Handle special case: If RecursiveDepth is 0, all recursive elements should be skipped.
182+
// Handle special case: If RecursiveDepth is 0, skip recursive types (same as parent) and nested children.
183+
// When RecursiveDepth is 0:
184+
// - stackCount = 0: We're generating first-level properties - skip if member type is same as parent (recursive)
185+
// - stackCount > 0: We're generating nested children (skip them)
183186
if (config.RecursiveDepth == 0)
184-
return true;
187+
{
188+
if (stackCount > 0)
189+
return true; // Skip nested children
190+
191+
// Skip recursive types (member type same as parent type) at first level
192+
if (autoMember.CachedType.CacheKey is { } memberCacheKey &&
193+
autoMember.ParentType.CacheKey is { } parentCacheKey &&
194+
memberCacheKey == parentCacheKey)
195+
{
196+
// Check if this is a complex type (not a collection/dictionary, which are handled separately)
197+
if (!autoMember.IsCollection && !autoMember.IsDictionary)
198+
{
199+
// Check if it has members to populate (making it a complex type)
200+
List<AutoMember>? membersToPopulate = context.Binder.GetMembersToPopulate(autoMember.CachedType, context.CacheService, config);
201+
if (membersToPopulate != null && membersToPopulate.Count > 0)
202+
return true; // Skip recursive complex types
203+
}
204+
}
205+
}
185206

186207
// Check if the stack count is below the recursive depth threshold.
187208
if (stackCount < config.RecursiveDepth)

src/Extensions/AutoGenerateContextExtension.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public static class AutoGenerateContextExtension
4444
/// <returns>The generated collection of instances.</returns>
4545
public static List<TType> GenerateMany<TType>(this AutoFakerContext context, int? count = null)
4646
{
47+
// When RecursiveDepth is 0 and we're generating nested items (stackCount > 0), return empty list
48+
if (context.Config.RecursiveDepth == 0 && context.TypesStack.Count > 0)
49+
{
50+
return [];
51+
}
52+
4753
count ??= context.Config.RepeatCount;
4854

4955
return GenerateMany<TType>(context, count.Value, false);

src/Generators/Types/DictionaryGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ object IAutoFakerGenerator.Generate(AutoFakerContext context)
6969
}
7070
else
7171
{
72-
// Custom IDictionary<,> fallback (cant avoid interface calls)
72+
// Custom IDictionary<,> fallback (cant avoid interface calls)
7373
// If it *happens* to be a Dictionary under the hood, ensure capacity
7474
if (items is Dictionary<TKey, TValue> d2)
7575
d2.EnsureCapacity(target);
@@ -87,7 +87,7 @@ object IAutoFakerGenerator.Generate(AutoFakerContext context)
8787
continue;
8888

8989
// Using ContainsKey+indexer would be two lookups; Add throws on dup, so try/catch is ok but slower.
90-
// Interface doesnt expose TryAdd; if dups are possible, you can guard:
90+
// Interface doesnt expose TryAdd; if dups are possible, you can guard:
9191
if (items is Dictionary<TKey, TValue> d3)
9292
{
9393
_ = d3.TryAdd(key, val);

test/Soenneker.Utils.AutoBogus.Tests/AutoFakerTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,39 @@ public void Generate_with_recursive_depth_0_should_generate()
508508
testClass.Child.Should().BeNull();
509509
}
510510

511+
[Fact]
512+
public void Generate_with_recursive_depth_0_should_set_first_level_properties_but_not_children()
513+
{
514+
var faker = new AutoFaker
515+
{
516+
Config =
517+
{
518+
RecursiveDepth = 0
519+
}
520+
};
521+
522+
var testClass = faker.Generate<TestClassWithRecursiveConstructor>();
523+
524+
testClass.Name.Should().NotBeNull();
525+
testClass.Age.Should().NotBe(0);
526+
527+
testClass.Child.Should().BeNull();
528+
529+
var order = faker.Generate<Order>();
530+
531+
order.Id.Should().NotBe(0);
532+
order.Code.Should().NotBeNull();
533+
order.Status.Should().BeDefined();
534+
order.DateCreated.Should().NotBe(default);
535+
536+
order.Items.Should().NotBeNull();
537+
order.Items.Should().BeEmpty();
538+
order.Discounts.Should().NotBeNull();
539+
order.Discounts.Should().BeEmpty();
540+
order.Comments.Should().NotBeNull();
541+
order.Comments.Should().BeEmpty();
542+
}
543+
511544
[Fact]
512545
public void Generate_with_recursive_depth_1_should_generate()
513546
{

0 commit comments

Comments
 (0)