Save time and your sanity with NBuilder

Posted on

Building maintainable unit test data quickly with NBuilder

Anyone who has worked on a unit test project for any length of time will tell you, setting up the vast amount of test data required for the process is a guaranteed way to cut through time and sanity. Depending on the complexity of the test, there are times when building and arranging the test data becomes more complex than actually mocking the relevant classes for the test. When you combine that with the possibility of test data changing format in the future, this becomes counter-productive.

That’s where NBuilder comes in. NBuilder is a Nuget package which, as the project’s overview states, “allows you to rapidly create test data”. It does this by instantiating objects given to it, and automatically assigning values to any properties or public fields built into the .NET framework (int, string, etc.). This is convenient, and drastically reduces the length of time spent generating test data. Here’s a couple of worked examples where NBuilder can save time and effort.

 

Single Object example

Let’s look at a simple worked example of where NBuilder can be used to effectively cut down code. Consider we have the following Address class, which has a number of location variables.

 

public class Address
{
    public string addressLine1;
    public string addressLine2;
    public string addressLine3;
    public string postTown;
    public string postCode;
    public string county;
}

Along with this, we have a business logic method, BuildAddress, which takes an Address object, before building a full address string from it, in the following format:

“addressLine1, addressLine2, addressLine3, postTown, postcode, county”

Now, normally to achieve full code coverage for this method, we would have to assign values, and generate the full address, as in the following unit test:

 

[TestMethod, TestCategory("Unit Test")]
public void ReturnedAddressAsExpected_True()
{
        // Set up example data
        var address = new Address
        {
            addressLine1 = "1 Test Road",
            addressLine2 = "Test Street",
            addressLine3 = "Testington",
            postTown = "Testinshire",
            postCode = "L3D 73P",
            county = "North Testingshire"
        };

        var expectedCombined = String.Format("{0}, {1}, {2}, {3}, {4}, {5}", 
                                              address.addressLine1, 
                                              address.addressLine2, 
                                              address.addressLine3, 
                                              address.postTown, 
                                              address.postCode, 
                                              address.county);

        // Call the method to retrieve the address
        var result = logicMock.BuildAddress(address);

        // Ensure the address is as expected
        Assert.AreEqual(expectedCombined, result, "Addresses do not match");
    }

While this does achieve the desired result, the time spent setting up the test data far outweighs the rest of the test, and this kind of setup can be difficult to maintain – if another address variable was added to the Address class, this test, and any others which set up an Address object, would have to change. Let’s deal with this problem by rewriting the same test, this time using NBuilder.

 

[TestMethod, TestCategory("Unit Test")]
public void ReturnAddressAsExpected_NBuilder_True()
    {
        // Set up example data
        var address = Builder<Address>.CreateNew().Build();

        var expectedCombined = String.Format("{0}, {1}, {2}, {3}, {4}, {5}", 
                                              address.addressLine1, 
                                              address.addressLine2, 
                                              address.addressLine3, 
                                              address.postTown, 
                                              address.postCode, 
                                              address.county);

        // Call the method to retrieve the address
        var result = logicMock.BuildAddress(address);

        // Ensure the address is as expected
        Assert.AreEqual(expectedCombined, result, "Addresses do not match");
    }

 

 

This test achieves the same results, but has been reduced significantly in size. By generating the Address object through the use of NBuilder, each of the address fields is automatically populated, as shown here:

AddressAutomaticallyFilled

 

In addition, were the Address class to be updated, only the expectedCombined variable would need to be altered, rather than the setup too, since the NBuilder setup would continue to automatically populate any additional field added.

 

Object Hierarchy example

We’ve seen above how NBuilder can be used with great effectiveness to cut down on test data setup by automatically assigning values to object types like strings, but what happens when we have to use types which are not built into .NET?

Let’s go back to the Address class and alter it somewhat

 

public class Address
{
    public string addressLine1;
    public string addressLine2;
    public string addressLine3;
    public string postTown;
    public Postcode postCode;
    public string county;
}

public class Postcode
{
    public string incode;
    public string outcode;
}

 

As you can see, the postcode is now comprised of its own class, made up of two strings. Ordinarily, we would now have to create an Address object, and a Postcode object, but we can easily use NBuilder.

Now, since NBuilder will only automatically assign values to types contained in the .NET framework, it cannot assign one to Postcode, without a little tinkering. This is where the With qualifier comes into play. Using this allows us to specify values, i.e.

NBuilder<Address>.CreateNew().With(addressLine1, “1 Test Street”).Build()

would yield an Address with all values automatically assigned bar addressLine1, which would have the “1 Test Street” value instead. So how can this be used for hierarchical assigning? Let’s alter our test again:

 

[TestMethod, TestCategory("Unit Test")]
        public void ReturnAddressAsExpected_NBuilder_True()
        {
            // Set up example data
            var builtPostcode = Builder<Postcode>.CreateNew().Build();

            var address = Builder<Address>.CreateNew()
                .With(x => x.postCode, builtPostcode)
                .Build();

            var expectedCombined = String.Format("{0}, {1}, {2}, {3}, {4} {5}, {6}", 
                                              address.addressLine1,
                                              address.addressLine2, 
                                              address.addressLine3, 
                                              address.postTown, 
                                              address.postCode.incode, 
                                              address.postCode.outcode, 
                                              address.county);

            // Call the method to retrieve the address
            var result = logicMock.BuildAddress(address);

            // Ensure the address is as expected
            Assert.AreEqual(expectedCombined, result, "Addresses do not match");
        }

 

As you can see, we have created a second builder for Postcode, and included it in the Address builer through the use of the with qualifier. Here’s the generated Address object, with all postcode values filled:

 

PostcodeGenerated

 

 

Again, this is efficient, easily maintainable, and cuts down on the time spent generating test data.

 

Final Thoughts

Hopefully the above examples have demonstrated why NBuilder is such a useful tool when working for any length of time with a unit test project. While there’s no need to use it for every test you write, when the complexity of setting up the test data outweighs that of writing the remainder of the test, I would suggest to at least consider NBuilder. With any luck, it will save you time, effort, and prevent the headaches brought on by staring at dummy data for too long.

Share on FacebookShare on LinkedInTweet about this on TwitterEmail this to someonePin on PinterestPrint this pageShare on Google+Share on Reddit

Leave a Reply

Your email address will not be published. Required fields are marked *