TestStack.Dossier provides you with the code infrastructure to easily and quickly generate test fixture data for your automated tests in a terse, readable and maintainable way using the Test Data Builder, anonymous value and equivalence class patterns.
C#
Latest commit 8afa6a1 Jan 8, 2017 @robdmoore robdmoore committed on GitHub Merge pull request #63 from TestStack/get-builders
Added ListBuilder.GetBuiders method

README.md

TestStack.Dossier

TestStack.Dossier provides you with the code infrastructure to easily and quickly generate test fixture data for your automated tests in a terse, readable and maintainable way using the Test Data Builder, anonymous value and equivalence class patterns.

For more information please see the blog post that gives the theory behind the approach this library was intended for and the presentation and example code that gives a concrete example of the usage of the library (and the theory behind it).

TestStack.Dossier is integrated with NSubstitute for proxy/mock/substitute object generation and AutoFixture for anonymous value generation. Version 1 was integrated with NBuilder for list generation, but that is now replaced with internal code that uses Castle Dynamic Proxy (il-merged into the dll) for an even terser syntax.

Prior to v2.0 this library was known as NTestDataBuilder.

Getting started - building a single object

  1. Install-Package TestStack.Dossier

  2. Are you building a DTO, view model, or other class you don't want to write a custom test data builder class for? If so then check out our generic test data builder implementation

  3. If you want to build a custom builder class, e.g. for a domain object, so you can use the builder as documentation and also to make the experience of building that class in your tests more rich then you need to extend the TestDataBuilder class like in the following code example:

    // Customer.cs
    
    public class Customer
    {
        protected Customer() {}
    
        public Customer(string firstName, string lastName, int yearJoined)
        {
            if (string.IsNullOrEmpty(firstName))
                throw new ArgumentNullException("firstName");
            if (string.IsNullOrEmpty(lastName))
                throw new ArgumentNullException("lastName");
    
            FirstName = firstName;
            LastName = lastName;
            YearJoined = yearJoined;
        }
    
        public virtual int CustomerForHowManyYears(DateTime since)
        {
            if (since.Year < YearJoined)
                throw new ArgumentException("Date must be on year or after year that customer joined.", "since");
            return since.Year - YearJoined;
        }
    
        public virtual string FirstName { get; private set; }
        public virtual string LastName { get; private set; }
        public virtual int YearJoined { get; private set; }
    }
    
    // CustomerBuilder.cs
    
    // Yep - you have to provide the custom builder type in as a generic type argument
    //   it's a bit weird, but necessary for the fluent chaining to work from the base class
    public class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
    {
        public CustomerBuilder()
        {
            // Can set up defaults here - any that you don't set or subsequently override
            //   will have an anonymous value generated by default
            WhoJoinedIn(2013);
        }
    
        // Note: the methods are virtual - this is important if you want to build lists (as per below)
        public virtual CustomerBuilder WithFirstName(string firstName)
        {
            return Set(x => x.FirstName, firstName);
        }
    
        // Note: we typically only start with the methods that are strictly needed so the
        //   builders are quick to write and aren't bloated 
        public virtual CustomerBuilder WithLastName(string lastName)
        {
            return Set(x => x.LastName, lastName);
        }
    
        public virtual CustomerBuilder WhoJoinedIn(int yearJoined)
        {
            return Set(x => x.YearJoined, yearJoined);
        }
    
        // This method is optional, by default it uses `BuildUsing<PublicPropertySettersFactory>()`
        protected override Customer BuildObject()
        {
            return BuildUsing<CallConstructorFactory>();
            // or, if you need more control / can't use the auto-construction assumptions
            return new Customer(
                Get(x => x.FirstName),
                Get(x => x.LastName),
                Get(x => x.YearJoined)
            );
        }
    }
    
  4. Use the builder in a test, e.g.

    var customer = new CustomerBuilder()
        .WithFirstName("Robert")
        .Build();
    
  5. Consider using the Object Mother pattern in combination with the builders, see my blog post for a description of how I use this library.

Getting started - building a list of objects

This library allows you to build a list of entities fluently and tersely. Here is an example:

var customers = CustomerBuilder.CreateListOfSize(5)
    .TheFirst(1).WithFirstName("First")
    .TheNext(1).WithLastName("Next Last")
    .TheLast(1).WithLastName("Last Last")
    .ThePrevious(2).With(b => b.WithLastName("last" + (++i).ToString()))
    .All().WhoJoinedIn(1999)
    .BuildList();

This would create the following (represented as json) - note the anonymous values that are generated:

[
    {
        "FirstName":"First",
        "LastName":"LastNameff51d5e5-9ce4-4710-830e-9042cfd48a8b",
        "YearJoined":1999
    },
    {
        "FirstName":"FirstName7b08da9c-8c13-47f7-abe9-09b73b935e1f",
        "LastName":"Next Last",
        "YearJoined":1999
    },
    {
        "FirstName":"FirstName836d4c54-b227-4c1b-b684-de4cd940c251",
        "LastName":"last1",
        "YearJoined":1999
    },
    {
        "FirstName":"FirstName5f53e895-921e-4130-8ed8-610b017f3b9b",
        "LastName":"last2",
        "YearJoined":1999
    },
    {
        "FirstName":"FirstName9cf6b05f-38aa-47c1-9fd7-e3c1009cf3e4",
        "LastName":"Last Last",
        "YearJoined":1999
    }
]

The same works with the generic Builder<T> implementation too.

Documentation

More comprehensive documentation is available on our documentation website.

Contributions / Questions

If you would like to contribute to this project then feel free to communicate with us via Twitter (@teststacknet) or alternatively submit a pull request / issue.

Feel free to check out our up-for-grabs issues if you don't know where to start.