|
1 | 1 | NTestDataBuilder |
2 | | ----------------- |
| 2 | +================ |
3 | 3 |
|
4 | 4 | NTestDataBuilder provides you with the code instructure to easily and quickly generate test fixture data for your automated tests in a terse, readable and maintainable way using the Test Data Builder pattern. |
5 | 5 |
|
6 | 6 | For more information please see the [blog post](http://robdmoore.id.au/blog/2013/05/26/test-data-generation-the-right-way-object-mother-test-data-builders-nsubstitute-nbuilder/) that gives the theory behind the approach this library was intended for. |
7 | 7 |
|
8 | 8 | NTestDataBuilder is integrated with NSubstitute for proxy/mock/substitute object generation and NBuilder for terse list generation. |
9 | 9 |
|
| 10 | +How do I get started? |
| 11 | +--------------------- |
| 12 | + |
| 13 | +1. `Install-Package NTestDataBuilder` |
| 14 | +2. Create a builder class for one of your objects, e.g. if you have a customer: |
| 15 | + |
| 16 | +```c# |
| 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 | + class CustomerBuilder : DataBuilder<Customer, CustomerBuilder> |
| 50 | + { |
| 51 | + public CustomerBuilder() |
| 52 | + { |
| 53 | + WithFirstName("Rob"); |
| 54 | + WithLastName("Moore"); |
| 55 | + WhoJoinedIn(2013); |
| 56 | + } |
| 57 | + |
| 58 | + public CustomerBuilder WithFirstName(string firstName) |
| 59 | + { |
| 60 | + Set(x => x.FirstName, firstName); |
| 61 | + return this; |
| 62 | + } |
| 63 | + |
| 64 | + public CustomerBuilder WithLastName(string lastName) |
| 65 | + { |
| 66 | + Set(x => x.LastName, lastName); |
| 67 | + return this; |
| 68 | + } |
| 69 | + |
| 70 | + public CustomerBuilder WhoJoinedIn(int yearJoined) |
| 71 | + { |
| 72 | + Set(x => x.YearJoined, yearJoined); |
| 73 | + return this; |
| 74 | + } |
| 75 | + |
| 76 | + protected override Customer BuildObject() |
| 77 | + { |
| 78 | + return new Customer( |
| 79 | + Get(x => x.FirstName), |
| 80 | + Get(x => x.LastName), |
| 81 | + Get(x => x.YearJoined) |
| 82 | + ); |
| 83 | + } |
| 84 | + } |
| 85 | +``` |
| 86 | + |
| 87 | +3. Use the builder in a test, e.g. |
| 88 | + |
| 89 | +```c# |
| 90 | + var customer = new CustomerBuilder().WithFirstName("Robert").Build(); |
| 91 | +``` |
| 92 | + |
| 93 | +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. |
| 94 | + |
| 95 | +How can I create a list of entities using my builders? |
| 96 | +------------------------------------------------------ |
| 97 | + |
| 98 | +This library integrates with [NBuilder](http://nbuilder.org/) for generating lists of entities, this means you can call the `CreateListOfSize` static method on your builder class and then use NBuilder syntax from there. Then when you are ready to create a list of entities call the `BuildList` method rather than the usual NBuilder `Build` method. e.g.: |
| 99 | + |
| 100 | +```c# |
| 101 | + var customers = CustomerBuilder.CreateListOfSize(3) |
| 102 | + .TheFirst(2).With(b => b.WithFirstName("Rob")) |
| 103 | + .TheNext(1).With(b => b.WithFirstName("Matt")) |
| 104 | + .BuildList<Customer, CustomerBuilder>(); |
| 105 | +``` |
| 106 | + |
| 107 | +If you don't want to have to specify the type of object and builder everywhere then simply add a couple of extension methods next to your builder class: |
| 108 | + |
| 109 | +```c# |
| 110 | + static class CustomerBuilderExtensions |
| 111 | + { |
| 112 | + public static IList<Customer> BuildList(this IOperable<CustomerBuilder> list) |
| 113 | + { |
| 114 | + return list.BuildList<Customer, CustomerBuilder>(); |
| 115 | + } |
| 116 | + |
| 117 | + public static IList<Customer> BuildList(this IListBuilder<CustomerBuilder> list) |
| 118 | + { |
| 119 | + return list.BuildList<Customer, CustomerBuilder>(); |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + // Then your test can use: |
| 124 | + |
| 125 | + var customers = CustomerBuilder.CreateListOfSize(3) |
| 126 | + .TheFirst(2).With(b => b.WithFirstName("Rob")) |
| 127 | + .TheNext(1).With(b => b.WithFirstName("Matt")) |
| 128 | + .BuildList(); |
| 129 | +``` |
| 130 | + |
| 131 | +How can I create proxy objects? |
| 132 | +------------------------------- |
| 133 | + |
| 134 | +This library integrates with [NSubstitute](http://nsubstitute.github.io/) for generating proxy objects, this means you can call the `AsProxy` method on your builder to request that the result from calling `Build` will be an NSubstitute proxy with the public properties set to return the values you have specified via your builder, e.g. |
| 135 | + |
| 136 | + var customer = CustomerBuilder.WithFirstName("Rob").AsProxy().Build(); |
| 137 | + customer.CustomerForHowManyYears(Arg.Any<DateTime>()).Returns(10); |
| 138 | + var name = customer.FirstName; // "Rob" |
| 139 | + var years = customer.CustomerForHowManyYears(DateTime.Now); // 10 |
| 140 | + |
| 141 | +If you need to alter the proxy before calling `Build` to add complex behaviours that can't be expressed by the default public properties returns values then you can override the `AlterProxy` method in your builder, e.g. |
| 142 | + |
| 143 | +```c# |
| 144 | + class CustomerBuilder : DataBuilder<Customer, CustomerBuilder> |
| 145 | + { |
| 146 | + // ... |
| 147 | + |
| 148 | + private int _years; |
| 149 | + |
| 150 | + public CustomerBuilder HasBeenMemberForYears(int years) |
| 151 | + { |
| 152 | + _years = years; |
| 153 | + return this; |
| 154 | + } |
| 155 | + |
| 156 | + protected override void AlterProxy(Customer proxy) |
| 157 | + { |
| 158 | + proxy.CustomerForHowManyYears(Arg.Any<DateTime>()).Returns(_years); |
| 159 | + } |
| 160 | + |
| 161 | + // ... |
| 162 | + } |
| 163 | + |
| 164 | + // Then in your test you can use: |
| 165 | + |
| 166 | + var customer = new CustomerBuilder().AsProxy().HasBeenMemberForYears(10); |
| 167 | + var years = customer.CustomerForHowManyYears(DateTime.Now); // 10 |
| 168 | +``` |
| 169 | + |
| 170 | +Why does NTestDataBuilder have NSubstitute and NBuilder as dependencies? |
| 171 | +------------------------------------------------------------------------ |
| 172 | + |
| 173 | +NTestDataBuilder is an opinionated framework and as such prescribes how to build your fixture data, including how to build lists and how to build mock objects. Because of this we have decided to bundle it with the two best of breed libraries for this purpose: NBuilder and NSubstitute. |
| 174 | + |
| 175 | +This allows for this library to provide a rich value-add on top of the basics of tracking properties in a dictionary in the `DataBuilder` base class. If you want to use different libraries or want a cut down version that doesn't come with NSubstitute or NBuilder and the extra functionality they bring then take the `DataBuilder.cs` file and cut out the bits you don't want - open source ftw :). |
| 176 | + |
| 177 | +If you have a suggestion for the library that can incorporate this value-add without bundling these libraries feel free to submit a pull request. |
| 178 | + |
| 179 | +Contributions / Questions |
| 180 | +------------------------- |
| 181 | + |
| 182 | +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. |
0 commit comments