You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -12,76 +12,79 @@ Prior to v2.0 this library was known as NTestDataBuilder.
12
12
13
13
1.`Install-Package TestStack.Dossier`
14
14
15
-
2. Create a builder class for one of your objects, e.g. if you have a customer:
16
-
17
-
// Customer.cs
18
-
19
-
public class Customer
20
-
{
21
-
protected Customer() {}
22
-
23
-
public Customer(string firstName, string lastName, int yearJoined)
24
-
{
25
-
if (string.IsNullOrEmpty(firstName))
26
-
throw new ArgumentNullException("firstName");
27
-
if (string.IsNullOrEmpty(lastName))
28
-
throw new ArgumentNullException("lastName");
29
-
30
-
FirstName = firstName;
31
-
LastName = lastName;
32
-
YearJoined = yearJoined;
33
-
}
34
-
35
-
public virtual int CustomerForHowManyYears(DateTime since)
36
-
{
37
-
if (since.Year < YearJoined)
38
-
throw new ArgumentException("Date must be on year or after year that customer joined.", "since");
39
-
return since.Year - YearJoined;
40
-
}
41
-
42
-
public virtual string FirstName { get; private set; }
43
-
public virtual string LastName { get; private set; }
44
-
public virtual int YearJoined { get; private set; }
45
-
}
46
-
47
-
// CustomerBuilder.cs
48
-
49
-
public class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
50
-
{
51
-
public CustomerBuilder()
52
-
{
53
-
// Can set up defaults here - any that you don't set will have an anonymous value generated by default.
54
-
WhoJoinedIn(2013);
55
-
}
56
-
57
-
public CustomerBuilder WithFirstName(string firstName)
58
-
{
59
-
return Set(x => x.FirstName, firstName);
60
-
}
61
-
62
-
public CustomerBuilder WithLastName(string lastName)
63
-
{
64
-
return Set(x => x.LastName, lastName);
65
-
}
66
-
67
-
public CustomerBuilder WhoJoinedIn(int yearJoined)
68
-
{
69
-
return Set(x => x.YearJoined, yearJoined);
70
-
}
71
-
72
-
protected override Customer BuildObject()
73
-
{
74
-
return new Customer(
75
-
Get(x => x.FirstName),
76
-
Get(x => x.LastName),
77
-
Get(x => x.YearJoined)
78
-
);
79
-
}
80
-
}
15
+
2. Create a builder class for one of your domain objects, e.g. if you have a customer:
16
+
17
+
// Customer.cs
18
+
19
+
public class Customer
20
+
{
21
+
protected Customer() {}
22
+
23
+
public Customer(string firstName, string lastName, int yearJoined)
24
+
{
25
+
if (string.IsNullOrEmpty(firstName))
26
+
throw new ArgumentNullException("firstName");
27
+
if (string.IsNullOrEmpty(lastName))
28
+
throw new ArgumentNullException("lastName");
29
+
30
+
FirstName = firstName;
31
+
LastName = lastName;
32
+
YearJoined = yearJoined;
33
+
}
34
+
35
+
public virtual int CustomerForHowManyYears(DateTime since)
36
+
{
37
+
if (since.Year < YearJoined)
38
+
throw new ArgumentException("Date must be on year or after year that customer joined.", "since");
39
+
return since.Year - YearJoined;
40
+
}
41
+
42
+
public virtual string FirstName { get; private set; }
43
+
public virtual string LastName { get; private set; }
44
+
public virtual int YearJoined { get; private set; }
45
+
}
46
+
47
+
// CustomerBuilder.cs
48
+
49
+
public class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
50
+
{
51
+
public CustomerBuilder()
52
+
{
53
+
// Can set up defaults here - any that you don't set or subsequently override will have an anonymous value generated by default.
54
+
WhoJoinedIn(2013);
55
+
}
56
+
57
+
// Note: the methods are virtual - this is important if you want to build lists (as per below)
58
+
public virtual CustomerBuilder WithFirstName(string firstName)
59
+
{
60
+
return Set(x => x.FirstName, firstName);
61
+
}
62
+
63
+
public virtual CustomerBuilder WithLastName(string lastName)
64
+
{
65
+
return Set(x => x.LastName, lastName);
66
+
}
67
+
68
+
public virtual CustomerBuilder WhoJoinedIn(int yearJoined)
69
+
{
70
+
return Set(x => x.YearJoined, yearJoined);
71
+
}
72
+
73
+
protected override Customer BuildObject()
74
+
{
75
+
return new Customer(
76
+
Get(x => x.FirstName),
77
+
Get(x => x.LastName),
78
+
Get(x => x.YearJoined)
79
+
);
80
+
// or
81
+
return BuildUsing<CallConstructorFactory>();
82
+
}
83
+
}
81
84
82
85
3. Use the builder in a test, e.g.
83
86
84
-
var customer = new CustomerBuilder().WithFirstName("Robert").Build();
87
+
var customer = new CustomerBuilder().WithFirstName("Robert").Build();
85
88
86
89
4. Consider using the Object Mother pattern in combination with the builders, see [my blog post](http://robdmoore.id.au/blog/2013/05/26/test-data-generation-the-right-way-object-mother-test-data-builders-nsubstitute-nbuilder/) for a description of how I use this library.
87
90
@@ -92,75 +95,134 @@ This library allows you to build a list of entities fluently and tersely. Here i
If you use the list builder functionality and get the following error:
137
141
138
-
> Castle.DynamicProxy.Generators.GeneratorExceptionCan not create proxy for type <YOUR_BUILDER_CLASS> because it is not accessible. Make it public, or internal and mark your assembly with [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] attribute, because assembly <YOUR_TEST_ASSEMBLY> is not strong-named.
142
+
> Castle.DynamicProxy.Generators.GeneratorException: Can not create proxy for type <YOUR_BUILDER_CLASS> because it is not accessible. Make it public, or internal and mark your assembly with [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] attribute, because assembly <YOUR_TEST_ASSEMBLY> is not strong-named.
139
143
140
144
Then you either need to:
141
145
142
146
* Make your builder class public
143
147
* Add the following to your `AssemblyInfo.cs` file: `[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]`
If you use the list builder functionality and get the following error:
152
+
153
+
> System.InvalidOperationException: Tried to build a list with a builder who has non-virtual method. Please make <METHOD_NAME> on type <YOUR_BUILDER_CLASS> virtual.
154
+
155
+
Then you need to mark all the public methods on your builder as virtual. This is because we are using Castle Dynamic Proxy to generate lists and it can't intercept non-virtual methods.
156
+
145
157
Create Entities Implicitly
146
158
--------------------------
147
-
In the previous examples, you have seen how to create entities *explicitly*, by calling the `Build()` and `BuildList()` methods. For the ultimate in terseness, you can omit these methods, and Dossier will *implicitly* call them for you. The one caveat is that you must explicitly declare the variable type rather than using the `var` keyword.
159
+
In the previous examples, you have seen how to create entities *explicitly*, by calling the `Build()` and `BuildList()` methods. For the ultimate in terseness, you can omit these methods, and Dossier will *implicitly* call them for you. The one caveat is that you must explicitly declare the variable type rather than using the `var` keyword (unless you are passing into a method with the desired type).
If you are building domain entities or other important classes having a custom builder class with intention-revealing method (e.g. WithFirstName) provides terseness (avoiding lambda expressions) and allows the builder class to start forming documentation about the usage of that object.
181
+
182
+
Sometimes though, you just want to build a class without that ceremony. Typically, we find that this applies for view models and DTOs.
183
+
184
+
In that instance you can use the generic Builder implementation as shown below:
The syntax is modelled closely against what NBuilder provides and the behaviour of the class should be very similar.
202
+
203
+
### Customising the construction of the object
204
+
205
+
By default the longest constructor of the class you specify will be called and then all properties (with public and private setters) will be set with values you specified (or anonymous values if none were specified).
206
+
207
+
Sometimes you might not want this behaviour, in which case you can specify a custom construction factory (see build objects without calling constructor section for explanation of factories) as shown below:
When you extend the `TestDataBuilder` as part of creating a custom builder you will be forced to override the abstract `BuildObject` method. You have full flexibility to call the constructor of your class directly as shown above, but you can also invoke some convention-based factories to speed up the creation of your builder (also shown above) using the `BuildUsing` method.
219
+
220
+
The `BuildUsing` method takes an instance of `IFactory`, of which you can create your own factory implementation that takes into account your own conventions or you can use one of the built-in ones:
221
+
222
+
*`AllPropertiesFactory` - Calls the longest constructor with builder values (or anonymous values if none set) based on case-insensitive match of constructor parameter names against property names and then calls the setter on all properties (public or private) with builder values (or anonymous values if none set)
223
+
*`PublicPropertySettersFactory` - Calls the longest constructor with builder values (or anonymous values if none set) based on case-insensitive match of constructor parameter names against property names and then calls the setter on all properties with public setters with builder values (or anonymous values if none set)
224
+
*`CallConstructorFactory` - Calls the longest constructor with builder values (or anonymous values if none set) based on case-insensitive match of constructor parameter names against property names
225
+
*`AutoFixtureFactory` - Asks AutoFixture to create an anonymous instance of the class (note: does **not** use any builder values or anonymous values from Dossier)
164
226
165
227
Anonymous Values and Equivalence Classes
166
228
----------------------------------------
@@ -220,4 +282,4 @@ If you have a suggestion for the library that can incorporate this value-add wit
220
282
Contributions / Questions
221
283
-------------------------
222
284
223
-
If you would like to contribute to this project then feel free to communicate with me via Twitter @robdmoore or alternatively submit a pull request / issue.
285
+
If you would like to contribute to this project then feel free to communicate with Rob via Twitter (@robdmoore) or alternatively submit a pull request / issue.
0 commit comments