Skip to content

Commit 0a994f0

Browse files
authored
Merge pull request #3 from DyegoMaas/feature/fill-with-property-names-behavior
New behavior: FillWithSequentialValuesBehavior
2 parents d5b0958 + 90ed621 commit 0a994f0

21 files changed

+657
-77
lines changed

README.md

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,40 @@ private class ProductFactory : MagicFactory<Product>
174174

175175
By default, it will not fill any properties, and it is up to you to fill any properties.
176176

177+
#### FillWithSequentialValuesBehavior
178+
179+
With this behavior, ForeverFactory will recursively initialize every property it can with sequential values. This is similar to the default behavior of NBuilder:
180+
181+
```csharp
182+
var people = MagicFactory
183+
.For<ClassWithInteger>()
184+
.WithBehavior(new FillWithSequentialValuesBehavior())
185+
.Many(100)
186+
.Build();
187+
188+
people[0].Name.Should().Be("Name1");
189+
people[0].Age.Should().Be(1);
190+
people[0].Address.ZipCode.Should().Be("ZipCode1");
191+
people[1].Name.Should().Be("Name2");
192+
people[1].Age.Should().Be(2);
193+
people[1].Address.ZipCode.Should().Be("ZipCode2");
194+
195+
public class Customer
196+
{
197+
public string Name { get; set; }
198+
public int Age { get; set; }
199+
public Address Address { get; set; }
200+
}
201+
202+
public class Address
203+
{
204+
public string ZipCode { get; set; }
205+
}
206+
```
207+
177208
#### FillWithEmptyValuesBehavior
178209

179-
With this behavior, ForeverFactory will recursively initialize every property it can. For example, the following class structure will resolve as shown below?
210+
With this behavior, ForeverFactory will recursively initialize every property it can with empty values. For example, the following class structure will resolve as shown below?
180211

181212
```csharp
182213
public class Customer
@@ -196,15 +227,17 @@ public class Address
196227
The table below shows test done with Benchmark Dotnet tool comparing equivalent scenarios in both `Forever Factory` and `NBuilder`:
197228

198229
```csv
199-
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated |
200-
|------------------------------------- |---------------:|-------------:|-------------:|--------:|--------:|----------:|
201-
| BuildSingleObjectForeverFactory | 683.6 ns | 7.34 ns | 6.86 ns | 0.1373 | - | 1,152 B |
202-
| BuildSingleObjectNBuilder | 1,939.7 ns | 20.81 ns | 19.47 ns | 0.0935 | - | 784 B |
203-
| BuildThousandObjectsForeverFactory | 243,524.0 ns | 3,502.33 ns | 2,924.61 ns | 53.7109 | 5.8594 | 449,403 B |
204-
| BuildThousandObjectsNBuilder | 1,555,241.7 ns | 17,075.19 ns | 14,258.56 ns | 76.1719 | 15.6250 | 653,402 B |
230+
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated |
231+
|---------------------------------------------------------- |---------------:|-------------:|-------------:|--------:|--------:|----------:|
232+
| BuildSingleObjectForeverFactory | 658.8 ns | 8.06 ns | 6.73 ns | 0.1373 | - | 1,152 B |
233+
| BuildSingleObjectNBuilder | 1,764.0 ns | 8.72 ns | 8.16 ns | 0.0935 | - | 784 B |
234+
| BuildThousandObjectsForeverFactory | 255,085.5 ns | 1,757.46 ns | 1,643.93 ns | 53.7109 | 5.8594 | 449,403 B |
235+
| BuildThousandObjectsNBuilder | 1,447,481.4 ns | 20,004.55 ns | 18,712.27 ns | 76.1719 | 15.6250 | 653,390 B |
236+
| BuildThousandObjectsFillingSequentialValuesForeverFactory | 524,209.5 ns | 3,039.96 ns | 2,538.51 ns | 80.0781 | 15.6250 | 673,170 B |
237+
| BuildThousandObjectsFillingSequentialValuesNBuilder | 984,430.7 ns | 7,177.55 ns | 6,362.71 ns | 70.3125 | 13.6719 | 589,282 B |
205238
```
206239

207-
The code to the tests is found [here](/tests/Benchmarks/Program.cs).
240+
The code to the tests is found [here](tests/Benchmarks/Program.cs).
208241

209242
## How to contribute
210243

@@ -233,7 +266,5 @@ dotnet stryker
233266

234267
- Allow to configure custom builder per builder (single builder and many builder)
235268
- Support custom constructor scoped by builder (for now, custom constructors are shared along the linked builders)
236-
- Allow configuration by rules
237-
- Allow sequences in numbers properties and things like email, etc
238269
- Support "smart" behavior, which identifies by convention which type of sequences and rules to apply to every property
239270
- Add the concept of "Localization Extensions", which could contain localized versions of the fluent API, translated for other languages, like, portuguese, spanish, etc

src/ForeverFactory/Behaviors/DoNotFillBehavior.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
namespace ForeverFactory.Behaviors
66
{
7+
/// <summary>
8+
/// This behavior do not fill automatically any properties. After instantiation, all properties of the object
9+
/// will have default values.
10+
/// </summary>
711
public class DoNotFillBehavior : Behavior
812
{
913
public override IEnumerable<Transform<T>> GetTransforms<T>()
Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using ForeverFactory.Generators.Transforms;
34
using ForeverFactory.Generators.Transforms.Factories;
45

56
namespace ForeverFactory.Behaviors
67
{
8+
/// <summary>
9+
/// This behavior will automatically set all string properties do "".
10+
/// Nested types will also be recursively initialized.
11+
/// </summary>
712
public class FillWithEmptyValuesBehavior : Behavior
813
{
14+
private readonly RecursiveTransformFactoryOptions _recursiveTransformFactoryOptions;
15+
16+
public FillWithEmptyValuesBehavior(Action<FillWithEmptyValuesBehaviorOptions> options = null)
17+
{
18+
var configuration = new FillWithEmptyValuesBehaviorOptions();
19+
options?.Invoke(configuration);
20+
21+
_recursiveTransformFactoryOptions = new RecursiveTransformFactoryOptions
22+
{
23+
EnableRecursiveInstantiation = configuration.Recursive
24+
};
25+
}
26+
927
public override IEnumerable<Transform<T>> GetTransforms<T>()
1028
{
11-
yield return TransformFactory.FillWithEmptyValues<T>();
29+
var factory = new FillWithEmptyStringTransformFactory(_recursiveTransformFactoryOptions);
30+
yield return factory.GetTransform<T>();
1231
}
1332
}
33+
34+
public class FillWithEmptyValuesBehaviorOptions
35+
{
36+
/// <summary>
37+
/// If enabled, nested classes will be instantiated and all its properties will be set with "".
38+
/// Default is true.
39+
/// </summary>
40+
public bool Recursive { get; set; } = true;
41+
}
1442
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using ForeverFactory.Generators.Transforms;
4+
using ForeverFactory.Generators.Transforms.Factories;
5+
6+
namespace ForeverFactory.Behaviors
7+
{
8+
/// <summary>
9+
/// This behavior will automatically set all properties sequential values.
10+
/// Nested objects will also be recursively initialized.
11+
/// </summary>
12+
/// <example>
13+
/// var persons = MagicFactory.For<ClassWithInteger>().Many(100).Build();
14+
/// person[0].Name == "Name1"
15+
/// person[0].Age == "1"
16+
/// person[1].Name == "Name2"
17+
/// person[1].Age == "2"
18+
/// ...
19+
/// </example>
20+
public class FillWithSequentialValuesBehavior : Behavior
21+
{
22+
private readonly RecursiveTransformFactoryOptions _recursiveTransformFactoryOptions;
23+
24+
public FillWithSequentialValuesBehavior(Action<FillWithSequentialValuesBehaviorOptions> options = null)
25+
{
26+
var configuration = new FillWithSequentialValuesBehaviorOptions();
27+
options?.Invoke(configuration);
28+
29+
_recursiveTransformFactoryOptions = new RecursiveTransformFactoryOptions
30+
{
31+
EnableRecursiveInstantiation = configuration.Recursive
32+
};
33+
}
34+
35+
public override IEnumerable<Transform<T>> GetTransforms<T>()
36+
{
37+
var factory = new FillWithSequentialValuesTransformFactory(_recursiveTransformFactoryOptions);
38+
yield return factory.GetTransform<T>();
39+
}
40+
}
41+
42+
public class FillWithSequentialValuesBehaviorOptions
43+
{
44+
/// <summary>
45+
/// If enabled, nested classes will be instantiated and all its properties will be set with sequential values.
46+
/// Default is true.
47+
/// </summary>
48+
public bool Recursive { get; set; } = true;
49+
}
50+
}

src/ForeverFactory/Generators/GeneratorNode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ private void ApplyTransformsToInstance(IEnumerable<GuardedTransform<T>> guardedT
6060
{
6161
foreach (var guardedTransform in guardedTransforms)
6262
if (guardedTransform.Guard.CanApply(instanceIndex))
63-
guardedTransform.Transform.ApplyTo(instance);
63+
guardedTransform.Transform.ApplyTo(instance, instanceIndex);
6464
}
6565
}
6666
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
namespace ForeverFactory.Generators.Transforms.Factories
6+
{
7+
internal abstract class BaseRecursiveTransformFactory : ITranformFactory
8+
{
9+
private readonly RecursiveTransformFactoryOptions _options;
10+
11+
protected BaseRecursiveTransformFactory(RecursiveTransformFactoryOptions options = null)
12+
{
13+
_options = options ?? new RecursiveTransformFactoryOptions();
14+
}
15+
16+
public Transform<T> GetTransform<T>()
17+
where T : class
18+
{
19+
var setMember = new Func<T, int, object>((instance, index) =>
20+
{
21+
FillPropertiesRecursively(instance, typeof(T), index);
22+
return instance;
23+
});
24+
return new ReflectedFuncTransform<T>(setMember);
25+
}
26+
27+
private void FillPropertiesRecursively(object instance, IReflect type, int index)
28+
{
29+
var propertyInfos = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
30+
foreach (var propertyInfo in propertyInfos)
31+
{
32+
var buildFunction = GetBuildFunction(propertyInfo, index);
33+
if (buildFunction == null)
34+
continue;
35+
36+
var propertyValue = buildFunction.Invoke();
37+
propertyInfo.SetValue(instance, propertyValue);
38+
39+
if (CanApplyRecursion(propertyInfo))
40+
FillPropertiesRecursively(propertyValue, propertyInfo.PropertyType, index);
41+
}
42+
}
43+
44+
private Func<object> GetBuildFunction(PropertyInfo propertyInfo, int index)
45+
{
46+
var buildFunction = GetBuildFunctionForSpecializedProperty(propertyInfo, index);
47+
if (buildFunction != null)
48+
return buildFunction;
49+
50+
if (_options.EnableRecursiveInstantiation is false)
51+
return null;
52+
53+
var parameterlessConstructor = propertyInfo.PropertyType
54+
.GetConstructors()
55+
.FirstOrDefault(x => x.GetParameters().Length == 0);
56+
if (parameterlessConstructor != null)
57+
return () => parameterlessConstructor.Invoke(Array.Empty<object>());
58+
59+
return null;
60+
}
61+
62+
protected abstract Func<object> GetBuildFunctionForSpecializedProperty(PropertyInfo propertyInfo, int index);
63+
64+
private bool CanApplyRecursion(PropertyInfo propertyInfo)
65+
{
66+
return _options.EnableRecursiveInstantiation && propertyInfo.PropertyType != typeof(string);
67+
}
68+
}
69+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace ForeverFactory.Generators.Transforms.Factories
5+
{
6+
internal class FillWithEmptyStringTransformFactory : BaseRecursiveTransformFactory
7+
{
8+
public FillWithEmptyStringTransformFactory(RecursiveTransformFactoryOptions options = null)
9+
: base(options)
10+
{
11+
}
12+
13+
protected override Func<object> GetBuildFunctionForSpecializedProperty(PropertyInfo propertyInfo, int index)
14+
{
15+
if (propertyInfo.PropertyType == typeof(string))
16+
return () => string.Empty;
17+
18+
return null;
19+
}
20+
}
21+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace ForeverFactory.Generators.Transforms.Factories
5+
{
6+
internal class FillWithSequentialValuesTransformFactory : BaseRecursiveTransformFactory
7+
{
8+
public FillWithSequentialValuesTransformFactory(RecursiveTransformFactoryOptions options = null)
9+
: base(options)
10+
{
11+
}
12+
13+
protected override Func<object> GetBuildFunctionForSpecializedProperty(PropertyInfo propertyInfo, int index)
14+
{
15+
var sequentialNumber = index + 1;
16+
17+
if (propertyInfo.PropertyType == typeof(string))
18+
return () => propertyInfo.Name + sequentialNumber;
19+
20+
if (propertyInfo.PropertyType == typeof(byte))
21+
return () =>
22+
{
23+
if (sequentialNumber > byte.MaxValue)
24+
return (byte)(sequentialNumber % byte.MaxValue);
25+
return (byte)sequentialNumber;
26+
};
27+
28+
if (propertyInfo.PropertyType == typeof(short))
29+
return () =>
30+
{
31+
if (sequentialNumber > short.MaxValue)
32+
return (short)(sequentialNumber % short.MaxValue);
33+
return (short)sequentialNumber;
34+
};
35+
36+
if (propertyInfo.PropertyType == typeof(ushort))
37+
return () =>
38+
{
39+
if (sequentialNumber > ushort.MaxValue)
40+
return (ushort)(sequentialNumber % ushort.MaxValue);
41+
return (ushort)sequentialNumber;
42+
};
43+
44+
if (propertyInfo.PropertyType == typeof(int))
45+
return () => sequentialNumber;
46+
47+
if (propertyInfo.PropertyType == typeof(uint))
48+
return () => (uint)sequentialNumber;
49+
50+
if (propertyInfo.PropertyType == typeof(long))
51+
return () => sequentialNumber;
52+
53+
if (propertyInfo.PropertyType == typeof(ulong))
54+
return () => (ulong)sequentialNumber;
55+
56+
if (propertyInfo.PropertyType == typeof(float))
57+
return () => sequentialNumber;
58+
59+
if (propertyInfo.PropertyType == typeof(double))
60+
return () => sequentialNumber;
61+
62+
if (propertyInfo.PropertyType == typeof(decimal))
63+
return () => Convert.ToDecimal(sequentialNumber);
64+
65+
return null;
66+
}
67+
}
68+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ForeverFactory.Generators.Transforms.Factories
2+
{
3+
internal interface ITranformFactory
4+
{
5+
Transform<T> GetTransform<T>()
6+
where T : class;
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ForeverFactory.Generators.Transforms.Factories
2+
{
3+
internal class RecursiveTransformFactoryOptions
4+
{
5+
public bool EnableRecursiveInstantiation { get; set; } = true;
6+
}
7+
}

0 commit comments

Comments
 (0)