Skip to content

Commit 4607939

Browse files
committed
Added a protected method called BuildByConstructor that allows a Builder to invoke an automated build of the object via a constructor.
1 parent 7ce4bb3 commit 4607939

4 files changed

Lines changed: 96 additions & 3 deletions

File tree

TestStack.Dossier.Tests/BuildTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,19 @@ public void GivenBuilder_WhenCallingSetImplicitly_ShouldOverrideValues()
8080
customer.LastName.ShouldBe("Lanningham");
8181
customer.YearJoined.ShouldBe(2014);
8282
}
83+
84+
[Fact]
85+
public void GivenBuilderUsingConstructorReflection_WhenCallingBuildExplicitly_ShouldOverrideValues()
86+
{
87+
Customer customer = new AutoConstructorCustomerBuilder()
88+
.WithFirstName("Bruce")
89+
.WithLastName("Wayne")
90+
.WhoJoinedIn(2012)
91+
.Build();
92+
93+
customer.FirstName.ShouldBe("Bruce");
94+
customer.LastName.ShouldBe("Wayne");
95+
customer.YearJoined.ShouldBe(2012);
96+
}
8397
}
8498
}
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+
using System.Text;
5+
using System.Threading.Tasks;
6+
using TestStack.Dossier.Tests.Entities;
7+
8+
namespace TestStack.Dossier.Tests.Builders
9+
{
10+
class AutoConstructorCustomerBuilder : TestDataBuilder<Customer, AutoConstructorCustomerBuilder>
11+
{
12+
protected override Customer BuildObject()
13+
{
14+
return BuildByConstructor();
15+
}
16+
17+
public AutoConstructorCustomerBuilder WithFirstName(string firstName)
18+
{
19+
return Set(x => x.FirstName, firstName);
20+
}
21+
22+
public AutoConstructorCustomerBuilder WithLastName(string lastName)
23+
{
24+
return Set(x => x.LastName, lastName);
25+
}
26+
27+
public AutoConstructorCustomerBuilder WhoJoinedIn(int year)
28+
{
29+
return Set(x => x.YearJoined, year);
30+
}
31+
}
32+
}

TestStack.Dossier.Tests/TestStack.Dossier.Tests.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<FileAlignment>512</FileAlignment>
1414
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
1515
<TargetFrameworkProfile />
16-
<NuGetPackageImportStamp>d4853673</NuGetPackageImportStamp>
16+
<NuGetPackageImportStamp>660882d1</NuGetPackageImportStamp>
1717
</PropertyGroup>
1818
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1919
<DebugSymbols>true</DebugSymbols>
@@ -54,6 +54,7 @@
5454
</Reference>
5555
</ItemGroup>
5656
<ItemGroup>
57+
<Compile Include="Builders\AutoConstructorCustomerBuilder.cs" />
5758
<Compile Include="ChildBuilderTests.cs" />
5859
<Compile Include="Entities\CustomerClass.cs" />
5960
<Compile Include="DataSources\DataSourceTests.cs" />
@@ -100,6 +101,9 @@
100101
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
101102
</Content>
102103
</ItemGroup>
104+
<ItemGroup>
105+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
106+
</ItemGroup>
103107
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
104108
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
105109
Other similar extension points exist, see Microsoft.Common.targets.

TestStack.Dossier/TestDataBuilder.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Linq.Expressions;
5+
using Ploeh.AutoFixture;
56
using TestStack.Dossier.Lists;
67

78
namespace TestStack.Dossier
@@ -15,7 +16,7 @@ public abstract class TestDataBuilder<TObject, TBuilder>
1516
where TObject : class
1617
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
1718
{
18-
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
19+
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
1920
private ProxyBuilder<TObject> _proxyBuilder;
2021

2122
/// <summary>
@@ -117,7 +118,7 @@ public TValue Get<TValue>(Expression<Func<TObject, TValue>> property)
117118
if (!Has(property))
118119
return Any.Get(property);
119120

120-
return (TValue)_properties[PropertyNameGetter.Get(property)];
121+
return (TValue) _properties[PropertyNameGetter.Get(property)];
121122
}
122123

123124
/// <summary>
@@ -182,5 +183,47 @@ protected virtual TChildBuilder GetChildBuilder<TChildObject, TChildBuilder>(Fun
182183
modifier(childBuilder);
183184
return childBuilder;
184185
}
186+
187+
/// <summary>
188+
/// Builds the object using the constructor with the most arguments.
189+
/// </summary>
190+
/// <returns></returns>
191+
protected TObject BuildByConstructor()
192+
{
193+
var longestConstructor = typeof (TObject)
194+
.GetConstructors()
195+
.OrderByDescending(x => x.GetParameters().Length)
196+
.FirstOrDefault();
197+
198+
if (longestConstructor == null) throw new ObjectCreationException();
199+
200+
var parameterValues = longestConstructor
201+
.GetParameters()
202+
.Select(x => CallGetWithType(x.Name, x.ParameterType));
203+
204+
return (TObject) longestConstructor.Invoke(parameterValues.ToArray());
205+
}
206+
207+
private object CallGetWithType(string propertyName, Type propertyType)
208+
{
209+
// Make a Func<TObj, TPropertyType>
210+
var expressionDelegateType = typeof(Func<,>).MakeGenericType(typeof(TObject), propertyType);
211+
212+
// Make an expression parameter of type TObj
213+
var tObjParameterType = Expression.Parameter(typeof(TObject));
214+
215+
var valueStoredInBuilder = typeof(TBuilder)
216+
.GetMethod("Get")
217+
.MakeGenericMethod(propertyType)
218+
.Invoke(this, new object[]
219+
{
220+
Expression.Lambda(
221+
expressionDelegateType,
222+
Expression.Property(tObjParameterType, propertyName),
223+
tObjParameterType)
224+
});
225+
226+
return valueStoredInBuilder;
227+
}
185228
}
186229
}

0 commit comments

Comments
 (0)