Skip to content

Commit eb6e338

Browse files
committed
Added ability to build a list without NBuilder with a much nicer syntax
1 parent fc2689d commit eb6e338

9 files changed

Lines changed: 250 additions & 21 deletions

File tree

NTestDataBuilder.Tests/Builders/CustomerBuilder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ public static IList<Customer> BuildList(this IListBuilder<CustomerBuilder> list)
1717
}
1818
}
1919

20-
class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
20+
public class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
2121
{
22-
public CustomerBuilder WithFirstName(string firstName)
22+
public virtual CustomerBuilder WithFirstName(string firstName)
2323
{
2424
return Set(x => x.FirstName, firstName);
2525
}
2626

27-
public CustomerBuilder WithLastName(string lastName)
27+
public virtual CustomerBuilder WithLastName(string lastName)
2828
{
2929
return Set(x => x.LastName, lastName);
3030
}
3131

32-
public CustomerBuilder WhoJoinedIn(int yearJoined)
32+
public virtual CustomerBuilder WhoJoinedIn(int yearJoined)
3333
{
3434
return Set(x => x.YearJoined, yearJoined);
3535
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Collections.Generic;
2+
using NTestDataBuilder.Lists;
3+
using NTestDataBuilder.Tests.Builders;
4+
using NTestDataBuilder.Tests.Entities;
5+
using NUnit.Framework;
6+
using Shouldly;
7+
8+
namespace NTestDataBuilder.Tests
9+
{
10+
class ListBuilderTests
11+
{
12+
[Test]
13+
public void WhenBuildingAListOfObjects_ThenThenTheListIsBuiltAndModifiedCorrectly()
14+
{
15+
var i = 0;
16+
var customers = CustomerBuilder.ListOFSize(5)
17+
.TheFirst(1).WithFirstName("First")
18+
.TheNext(1).WithLastName("Next Last")
19+
.TheLast(1).WithLastName("Last Last")
20+
.ThePrevious(2).With(b => b.WithLastName("last" + (++i).ToString()))
21+
.All().WhoJoinedIn(1999)
22+
.BuildList();
23+
24+
customers.ShouldBeAssignableTo<IList<Customer>>();
25+
customers.Count.ShouldBe(5);
26+
customers[0].FirstName.ShouldBe("First");
27+
customers[1].LastName.ShouldBe("Next Last");
28+
customers[2].LastName.ShouldBe("last1");
29+
customers[3].LastName.ShouldBe("last2");
30+
customers[4].LastName.ShouldBe("Last Last");
31+
customers.ShouldAllBe(c => c.YearJoined == 1999);
32+
}
33+
}
34+
}

NTestDataBuilder.Tests/NTestDataBuilder.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
<Reference Include="nunit.framework">
4242
<HintPath>..\packages\NUnit.2.6.2\lib\nunit.framework.dll</HintPath>
4343
</Reference>
44+
<Reference Include="Shouldly">
45+
<HintPath>..\packages\Shouldly.2.2.0\lib\net40\Shouldly.dll</HintPath>
46+
</Reference>
4447
<Reference Include="System" />
4548
<Reference Include="System.Core" />
4649
<Reference Include="Microsoft.CSharp" />
@@ -59,6 +62,7 @@
5962
<Compile Include="GetAnonymousTests.cs" />
6063
<Compile Include="GetOrDefaultTests.cs" />
6164
<Compile Include="GetSetTests.cs" />
65+
<Compile Include="ListBuilderTests.cs" />
6266
<Compile Include="Properties\AssemblyInfo.cs" />
6367
<Compile Include="ProxyBuilderTests.cs" />
6468
<Compile Include="TestHelpers\StaticAnonymousValueSupplier.cs" />

NTestDataBuilder.Tests/packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
44
<package id="NSubstitute" version="1.7.2.0" targetFramework="net40" />
55
<package id="NUnit" version="2.6.2" targetFramework="net40" />
6+
<package id="Shouldly" version="2.2.0" targetFramework="net40" />
67
</packages>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using Castle.DynamicProxy;
6+
using FizzWare.NBuilder;
7+
8+
namespace NTestDataBuilder.Lists
9+
{
10+
public static class ListBuilderGenerator
11+
{
12+
static ListBuilderGenerator()
13+
{
14+
Generator = new ProxyGenerator(true);
15+
}
16+
17+
public static ProxyGenerator Generator { get; private set; }
18+
}
19+
20+
public static class ListBuilderExtensions
21+
{
22+
public static TBuilder TheFirst<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b, int howMany)
23+
where TObject : class
24+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
25+
{
26+
return b.ListBuilder.TheFirst(howMany);
27+
}
28+
29+
public static TBuilder TheNext<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b, int howMany)
30+
where TObject : class
31+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
32+
{
33+
return b.ListBuilder.TheNext(howMany);
34+
}
35+
36+
public static TBuilder TheLast<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b, int howMany)
37+
where TObject : class
38+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
39+
{
40+
return b.ListBuilder.TheLast(howMany);
41+
}
42+
43+
public static TBuilder ThePrevious<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b, int howMany)
44+
where TObject : class
45+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
46+
{
47+
return b.ListBuilder.ThePrevious(howMany);
48+
}
49+
50+
public static TBuilder All<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b)
51+
where TObject : class
52+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
53+
{
54+
return b.ListBuilder.All();
55+
}
56+
57+
public static ListBuilder<TObject, TBuilder> With<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b, Func<TBuilder, TBuilder> modifier)
58+
where TObject : class
59+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
60+
{
61+
return b.ListBuilder.With(modifier);
62+
}
63+
64+
public static IList<TObject> BuildList<TObject, TBuilder>(this TestDataBuilder<TObject, TBuilder> b)
65+
where TObject : class
66+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
67+
{
68+
return b.ListBuilder.BuildList();
69+
}
70+
}
71+
72+
public class ListBuilderInterceptor<TObject, TBuilder> : IInterceptor
73+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
74+
where TObject : class
75+
{
76+
private readonly ListBuilder<TObject, TBuilder> _builder;
77+
78+
public ListBuilderInterceptor(ListBuilder<TObject, TBuilder> builder)
79+
{
80+
_builder = builder;
81+
}
82+
83+
public void Intercept(IInvocation invocation)
84+
{
85+
if (invocation.Method.ReturnType != typeof (TBuilder))
86+
{
87+
throw new InvalidOperationException("Non-fluent builder method invoked while creating a list of builders: " + invocation.Method.Name);
88+
}
89+
90+
_builder.Execute(invocation);
91+
invocation.ReturnValue = _builder.BuilderProxy;
92+
}
93+
}
94+
95+
public class ListBuilder<TObject, TBuilder>
96+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
97+
where TObject : class
98+
{
99+
private int _start = 0;
100+
private int _count = 0;
101+
private readonly List<TBuilder> _list;
102+
103+
public ListBuilder(int size)
104+
{
105+
BuilderProxy = (TBuilder) ListBuilderGenerator.Generator
106+
.CreateClassProxy(typeof (TBuilder), new ProxyGenerationOptions(new EnsureAllMethodsVirtual()), new ListBuilderInterceptor<TObject, TBuilder>(this));
107+
BuilderProxy.ListBuilder = this;
108+
_list = new List<TBuilder>();
109+
for (var i = 0; i < size; i++)
110+
_list.Add(new TBuilder());
111+
}
112+
113+
public TBuilder BuilderProxy { get; private set; }
114+
115+
public TBuilder TheFirst(int howMany)
116+
{
117+
_start = 0;
118+
_count = howMany;
119+
return BuilderProxy;
120+
}
121+
122+
public TBuilder TheNext(int howMany)
123+
{
124+
_start += _count;
125+
_count = howMany;
126+
return BuilderProxy;
127+
}
128+
129+
public TBuilder ThePrevious(int howMany)
130+
{
131+
_start -= howMany;
132+
_count = howMany;
133+
return BuilderProxy;
134+
}
135+
136+
public TBuilder TheLast(int howMany)
137+
{
138+
_start = _list.Count - howMany;
139+
_count = howMany;
140+
return BuilderProxy;
141+
}
142+
143+
public TBuilder All()
144+
{
145+
_start = 0;
146+
_count = _list.Count;
147+
return BuilderProxy;
148+
}
149+
150+
public ListBuilder<TObject, TBuilder> With(Func<TBuilder, TBuilder> modifier)
151+
{
152+
_list.Skip(_start)
153+
.Take(_count)
154+
.ToList()
155+
.ForEach(b => modifier(b));
156+
return this;
157+
}
158+
159+
public IList<TObject> BuildList()
160+
{
161+
return _list.Select(b => b.Build()).ToArray();
162+
}
163+
164+
internal void Execute(IInvocation invocation)
165+
{
166+
_list.Skip(_start)
167+
.Take(_count)
168+
.ToList()
169+
.ForEach(b => invocation.Method.Invoke(b, invocation.Arguments));
170+
}
171+
}
172+
173+
public class EnsureAllMethodsVirtual : IProxyGenerationHook
174+
{
175+
public void MethodsInspected()
176+
{
177+
}
178+
179+
public void NonProxyableMemberNotification(Type type, MemberInfo memberInfo)
180+
{
181+
if (new[]{"get_Any", "Build", "AsProxy", "Get", "GetOrDefault", "Set", "Has"}.Contains(memberInfo.Name))
182+
return;
183+
throw new InvalidOperationException(string.Format("Tried to build a list with a builder who has non-virtual method. Please make {0} on type {1} virtual.", memberInfo.Name, type.Name));
184+
}
185+
186+
public bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
187+
{
188+
return true;
189+
}
190+
}
191+
}

NTestDataBuilder/NTestDataBuilder.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
<DocumentationFile>bin\Release\NTestDataBuilder.XML</DocumentationFile>
3434
</PropertyGroup>
3535
<ItemGroup>
36+
<Reference Include="Castle.Core">
37+
<HintPath>..\packages\Castle.Core.3.3.1\lib\net40-client\Castle.Core.dll</HintPath>
38+
</Reference>
3639
<Reference Include="FizzWare.NBuilder">
3740
<HintPath>..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll</HintPath>
3841
</Reference>
@@ -51,6 +54,7 @@
5154
<Compile Include="AnonymousValueFixture.cs" />
5255
<Compile Include="EquivalenceClasses\StringEquivalenceClasses.cs" />
5356
<Compile Include="IAnonymousValueSupplier.cs" />
57+
<Compile Include="Lists\ListBuilder.cs" />
5458
<Compile Include="PropertyNameGetter.cs" />
5559
<Compile Include="Suppliers\DefaultValueSupplier.cs" />
5660
<Compile Include="Suppliers\DefaultStringValueSupplier.cs" />

NTestDataBuilder/TestDataBuilder.cs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,22 @@
22
using System.Collections.Generic;
33
using System.Linq.Expressions;
44
using FizzWare.NBuilder;
5+
using NTestDataBuilder.Lists;
56

67
namespace NTestDataBuilder
78
{
8-
/// <summary>
9-
/// Generates objects of type {T}.
10-
/// </summary>
11-
/// <typeparam name="T">The type of object this class generates</typeparam>
12-
public interface ITestDataBuilder<out T> where T : class
13-
{
14-
/// <summary>
15-
/// Build the object.
16-
/// </summary>
17-
/// <returns>The built object</returns>
18-
T Build();
19-
}
20-
219
/// <summary>
2210
/// Base class definining infrastructure for a class that generates objects of type {TObject}.
2311
/// </summary>
2412
/// <typeparam name="TObject">The type of object this class generates</typeparam>
2513
/// <typeparam name="TBuilder">The type for this class, yes this is a recursive type definition</typeparam>
26-
public abstract class TestDataBuilder<TObject, TBuilder> : ITestDataBuilder<TObject>
14+
public abstract class TestDataBuilder<TObject, TBuilder>
2715
where TObject : class
28-
where TBuilder : class, ITestDataBuilder<TObject>
16+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
2917
{
3018
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
3119
private ProxyBuilder<TObject> _proxyBuilder;
20+
internal ListBuilder<TObject, TBuilder> ListBuilder { get; set; }
3221

3322
/// <summary>
3423
/// Default Constructor.
@@ -133,6 +122,11 @@ public static IListBuilder<TBuilder> CreateListOfSize(int size)
133122
return Builder<TBuilder>.CreateListOfSize(size);
134123
}
135124

125+
public static NTestDataBuilder.Lists.ListBuilder<TObject, TBuilder> ListOFSize(int size)
126+
{
127+
return new ListBuilder<TObject, TBuilder>(size);
128+
}
129+
136130
/// <summary>
137131
/// Returns whether or not there is currently an explicit value recorded against the given property from {TObject}.
138132
/// </summary>

NTestDataBuilder/TestDataBuilderExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static class TestDataBuilderExtensions
1717
/// <param name="builderList">The NBuilder list of builders</param>
1818
/// <returns>The built list of objects</returns>
1919
public static IList<TObject> BuildList<TObject, TBuilder>(this IOperable<TBuilder> builderList)
20-
where TBuilder : ITestDataBuilder<TObject>
20+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
2121
where TObject : class
2222
{
2323
return builderList.Build().Select(b => b.Build()).ToList();
@@ -31,7 +31,7 @@ public static IList<TObject> BuildList<TObject, TBuilder>(this IOperable<TBuilde
3131
/// <param name="builderList">The NBuilder list of builders</param>
3232
/// <returns>The built list of objects</returns>
3333
public static IList<TObject> BuildList<TObject, TBuilder>(this IListBuilder<TBuilder> builderList)
34-
where TBuilder : ITestDataBuilder<TObject>
34+
where TBuilder : TestDataBuilder<TObject, TBuilder>, new()
3535
where TObject : class
3636
{
3737
return builderList.All().BuildList<TObject, TBuilder>();

NTestDataBuilder/packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
33
<package id="AutoFixture" version="3.20.0" targetFramework="net40" />
4+
<package id="Castle.Core" version="3.3.1" targetFramework="net40" />
45
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
56
<package id="NSubstitute" version="1.7.2.0" targetFramework="net40" />
67
</packages>

0 commit comments

Comments
 (0)