Skip to content

Commit 4f95399

Browse files
committed
Introduced Object Builders
I managed to break ConstructorBuilder when I moved it from TestDataBuilder
1 parent 6551bff commit 4f95399

9 files changed

Lines changed: 206 additions & 60 deletions

TestStack.Dossier.Tests/Builders/AutoConstructorCustomerBuilder.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
using TestStack.Dossier.Tests.Stubs.Entities;
1+
using TestStack.Dossier.ObjectBuilders;
2+
using TestStack.Dossier.Tests.Stubs.Entities;
23

34
namespace TestStack.Dossier.Tests.Builders
45
{
56
class AutoConstructorCustomerBuilder : TestDataBuilder<Customer, AutoConstructorCustomerBuilder>
67
{
78
protected override Customer BuildObject()
89
{
9-
return BuildByConstructor();
10+
return FactoryRegistry.Get<ConstructorObjectBuilder>().BuildObject(this);
1011
}
1112

1213
public AutoConstructorCustomerBuilder WithFirstName(string firstName)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Ploeh.AutoFixture;
2+
3+
namespace TestStack.Dossier.ObjectBuilders
4+
{
5+
/// <summary>
6+
/// Creates an instance of an object by setting all public and private properties.
7+
/// </summary>
8+
public class AllPropertiesObjectBuilder : IObjectBuilder
9+
{
10+
/// <inheritdoc />
11+
public TObject BuildObject<TObject, TBuilder>(TestDataBuilder<TObject, TBuilder> builder)
12+
where TObject : class
13+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
14+
{
15+
var model = builder.Any.Fixture.Create<TObject>();
16+
17+
var properties = Reflector.GetSettablePropertiesFor<TObject>();
18+
foreach (var property in properties)
19+
{
20+
if (property.CanWrite)
21+
{
22+
var val = builder.Get(property.PropertyType, property.Name);
23+
property.SetValue(model, val, null);
24+
}
25+
}
26+
27+
return model;
28+
}
29+
}
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Ploeh.AutoFixture;
2+
3+
namespace TestStack.Dossier.ObjectBuilders
4+
{
5+
/// <summary>
6+
/// Creates an instance of an object with AutoFixture.
7+
/// </summary>
8+
public class AutoFixtureObjectBuilder : IObjectBuilder
9+
{
10+
/// <inheritdoc />
11+
public TObject BuildObject<TObject, TBuilder>(TestDataBuilder<TObject, TBuilder> builder)
12+
where TObject : class
13+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
14+
{
15+
return builder.Any.Fixture.Create<TObject>();
16+
}
17+
}
18+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
using Ploeh.AutoFixture;
5+
6+
namespace TestStack.Dossier.ObjectBuilders
7+
{
8+
/// <summary>
9+
/// Builds the object using the constructor with the most arguments.
10+
/// </summary>
11+
public class ConstructorObjectBuilder : IObjectBuilder
12+
{
13+
/// <inheritdoc />
14+
public TObject BuildObject<TObject, TBuilder>(TestDataBuilder<TObject, TBuilder> builder)
15+
where TObject : class
16+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
17+
{
18+
var longestConstructor = typeof(TObject)
19+
.GetConstructors()
20+
.OrderByDescending(x => x.GetParameters().Length)
21+
.FirstOrDefault();
22+
23+
if (longestConstructor == null) throw new ObjectCreationException();
24+
25+
var parameterValues = longestConstructor
26+
.GetParameters()
27+
.Select(x => CallGetWithType(x.Name, x.ParameterType, typeof(TObject), typeof(TBuilder)));
28+
29+
return (TObject)longestConstructor.Invoke(parameterValues.ToArray());
30+
}
31+
32+
private object CallGetWithType(string propertyName, Type propertyType, Type objectType, Type builderType)
33+
{
34+
// Make a Func<TObj, TPropertyType>
35+
var expressionDelegateType = typeof(Func<,>).MakeGenericType(objectType, propertyType);
36+
37+
// Make an expression parameter of type TObj
38+
var tObjParameterType = Expression.Parameter(objectType);
39+
40+
var valueStoredInBuilder = builderType
41+
.GetMethods()
42+
.First(method => method.Name == "Get" && method.ContainsGenericParameters && method.GetGenericArguments().Length == 1)
43+
.MakeGenericMethod(propertyType)
44+
.Invoke(this, new object[]
45+
{
46+
Expression.Lambda(
47+
expressionDelegateType,
48+
Expression.Property(tObjParameterType, propertyName),
49+
tObjParameterType)
50+
});
51+
52+
return valueStoredInBuilder;
53+
}
54+
55+
}
56+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace TestStack.Dossier.ObjectBuilders
6+
{
7+
/// <summary>
8+
/// A static registry of object builder factories.
9+
/// </summary>
10+
public static class FactoryRegistry
11+
{
12+
private static List<Tuple<Type, IObjectBuilder>> _factories = new List<Tuple<Type, IObjectBuilder>>
13+
{
14+
new Tuple<Type, IObjectBuilder>(typeof(AllPropertiesObjectBuilder), new AllPropertiesObjectBuilder()),
15+
new Tuple<Type, IObjectBuilder>(typeof(PublicPropertiesObjectBuilder), new PublicPropertiesObjectBuilder()),
16+
new Tuple<Type, IObjectBuilder>(typeof(ConstructorObjectBuilder), new ConstructorObjectBuilder()),
17+
new Tuple<Type, IObjectBuilder>(typeof(AutoFixtureObjectBuilder), new AutoFixtureObjectBuilder())
18+
};
19+
20+
/// <summary>
21+
/// Provides access to specified factory.
22+
/// </summary>
23+
/// <typeparam name="T"></typeparam>
24+
/// <returns>The requested Factory</returns>
25+
public static IObjectBuilder Get<T>() where T : IObjectBuilder
26+
{
27+
return _factories.Where(x => x.Item1 == typeof (T))
28+
.Select(x => x.Item2)
29+
.First();
30+
}
31+
}
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace TestStack.Dossier.ObjectBuilders
2+
{
3+
/// <summary>
4+
/// Interface for object building strategies
5+
/// </summary>
6+
public interface IObjectBuilder
7+
{
8+
/// <summary>
9+
/// Takes a builder and generates an object of the specified type.
10+
/// </summary>
11+
/// <param name="builder">An instance of the TestDataBuilder.</param>
12+
/// <typeparam name="TObject">The generic type of the object that will be generated.</typeparam>
13+
/// <typeparam name="TBuilder">The generic type of the TestDataBuilder.</typeparam>
14+
/// <returns>An instance of the created object.</returns>
15+
TObject BuildObject<TObject, TBuilder>(TestDataBuilder<TObject, TBuilder> builder)
16+
where TObject : class
17+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new();
18+
}
19+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Ploeh.AutoFixture;
2+
3+
namespace TestStack.Dossier.ObjectBuilders
4+
{
5+
/// <summary>
6+
/// Creates an instance of an object by setting all public properties but not private properties.
7+
/// </summary>
8+
public class PublicPropertiesObjectBuilder : IObjectBuilder
9+
{
10+
/// <inheritdoc />
11+
public TObject BuildObject<TObject, TBuilder>(TestDataBuilder<TObject, TBuilder> builder)
12+
where TObject : class
13+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
14+
{
15+
var model = builder.Any.Fixture.Create<TObject>();
16+
17+
var properties = Reflector.GetSettablePropertiesFor<TObject>();
18+
foreach (var property in properties)
19+
{
20+
if (property.CanWrite && property.GetSetMethod().IsPublic)
21+
{
22+
var val = builder.Get(property.PropertyType, property.Name);
23+
property.SetValue(model, val, null);
24+
}
25+
}
26+
27+
return model;
28+
}
29+
}
30+
}

TestStack.Dossier/TestDataBuilder.cs

Lines changed: 12 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using System.Linq.Expressions;
5-
using System.Reflection;
6-
using Ploeh.AutoFixture;
74
using TestStack.Dossier.Lists;
5+
using TestStack.Dossier.ObjectBuilders;
86

97
namespace TestStack.Dossier
108
{
@@ -78,17 +76,9 @@ public static implicit operator List<TObject>(TestDataBuilder<TObject, TBuilder>
7876
/// <returns>The built object</returns>
7977
protected virtual TObject BuildObject()
8078
{
81-
var model = Any.Fixture.Create<TObject>();
82-
83-
var properties = Reflector.GetSettablePropertiesFor<TObject>();
84-
foreach (var property in properties)
85-
{
86-
if (property.CanWrite)
87-
{
88-
var val = Get(property.PropertyType, property.Name);
89-
property.SetValue(model, val, null);
90-
}
91-
}
79+
var model = FactoryRegistry
80+
.Get<AllPropertiesObjectBuilder>()
81+
.BuildObject(this);
9282

9383
return model;
9484
}
@@ -126,7 +116,7 @@ public TBuilder Set<TValue>(Expression<Func<TObject, TValue>> property, TValue v
126116
/// Gets the recorded value for the given property from {TObject} or an anonymous
127117
/// value if there isn't one specified.
128118
/// </summary>
129-
/// <typeparam name="TValue">The type of the property</typeparam>
119+
/// <typeparam name="TValue">The type of the property.</typeparam>
130120
/// <param name="property">A lambda expression specifying the property to retrieve the recorded value for</param>
131121
/// <returns>The recorded value of the property or an anonymous value for it</returns>
132122
public TValue Get<TValue>(Expression<Func<TObject, TValue>> property)
@@ -137,6 +127,13 @@ public TValue Get<TValue>(Expression<Func<TObject, TValue>> property)
137127
return (TValue)_properties[Reflector.GetPropertyNameFor(property)];
138128
}
139129

130+
/// <summary>
131+
/// Gets the recorded value for the given property from {type} or an anonymous
132+
/// value if there isn't one specified.
133+
/// </summary>
134+
/// <param name="type">The type of the property.</param>
135+
/// <param name="propertyName">The property name.</param>
136+
/// <returns></returns>
140137
public object Get(Type type, string propertyName)
141138
{
142139
if (!Has(propertyName))
@@ -216,48 +213,5 @@ protected virtual TChildBuilder GetChildBuilder<TChildObject, TChildBuilder>(Fun
216213
modifier(childBuilder);
217214
return childBuilder;
218215
}
219-
220-
/// <summary>
221-
/// Builds the object using the constructor with the most arguments.
222-
/// </summary>
223-
/// <returns></returns>
224-
protected virtual TObject BuildByConstructor()
225-
{
226-
var longestConstructor = typeof (TObject)
227-
.GetConstructors()
228-
.OrderByDescending(x => x.GetParameters().Length)
229-
.FirstOrDefault();
230-
231-
if (longestConstructor == null) throw new ObjectCreationException();
232-
233-
var parameterValues = longestConstructor
234-
.GetParameters()
235-
.Select(x => CallGetWithType(x.Name, x.ParameterType));
236-
237-
return (TObject) longestConstructor.Invoke(parameterValues.ToArray());
238-
}
239-
240-
private object CallGetWithType(string propertyName, Type propertyType)
241-
{
242-
// Make a Func<TObj, TPropertyType>
243-
var expressionDelegateType = typeof(Func<,>).MakeGenericType(typeof(TObject), propertyType);
244-
245-
// Make an expression parameter of type TObj
246-
var tObjParameterType = Expression.Parameter(typeof(TObject));
247-
248-
var valueStoredInBuilder = typeof(TBuilder)
249-
.GetMethods()
250-
.First(method => method.Name == "Get" && method.ContainsGenericParameters && method.GetGenericArguments().Length ==1)
251-
.MakeGenericMethod(propertyType)
252-
.Invoke(this, new object[]
253-
{
254-
Expression.Lambda(
255-
expressionDelegateType,
256-
Expression.Property(tObjParameterType, propertyName),
257-
tObjParameterType)
258-
});
259-
260-
return valueStoredInBuilder;
261-
}
262216
}
263217
}

TestStack.Dossier/TestStack.Dossier.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,19 @@
8080
<Compile Include="DataSources\Generators\RandomGenerator.cs" />
8181
<Compile Include="DataSources\Generators\SequentialGenerator.cs" />
8282
<Compile Include="EquivalenceClasses\StringEquivalenceClasses.cs" />
83+
<Compile Include="ObjectBuilders\AutoFixtureObjectBuilder.cs" />
84+
<Compile Include="ObjectBuilders\ConstructorObjectBuilder.cs" />
85+
<Compile Include="ObjectBuilders\FactoryRegistry.cs" />
86+
<Compile Include="ObjectBuilders\AllPropertiesObjectBuilder.cs" />
87+
<Compile Include="ObjectBuilders\PublicPropertiesObjectBuilder.cs" />
8388
<Compile Include="IAnonymousValueSupplier.cs" />
8489
<Compile Include="Lists\EnsureAllMethodsVirtual.cs" />
8590
<Compile Include="Lists\ListBuilder.cs" />
8691
<Compile Include="Lists\ListBuilderExtensions.cs" />
8792
<Compile Include="Lists\ListBuilderGenerator.cs" />
8893
<Compile Include="Lists\ListBuilderInterceptor.cs" />
8994
<Compile Include="NullingExpandoObject.cs" />
95+
<Compile Include="ObjectBuilders\IObjectBuilder.cs" />
9096
<Compile Include="Reflector.cs" />
9197
<Compile Include="Suppliers\DefaultEmailValueSupplier.cs" />
9298
<Compile Include="Suppliers\DefaultLastNameValueSupplier.cs" />

0 commit comments

Comments
 (0)