Skip to content

Bringer128/TestStack.Dossier

 
 

Repository files navigation

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 pattern.

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.

How do I get started?

  1. Install-Package TestStack.Dossier

  2. Create a builder class for one of your objects, e.g. if you have a customer:

     // 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
     
     public class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
     {
         public CustomerBuilder()
         {
     		// Can set up defaults here - any that you don't set will have an anonymous value generated by default.
             WhoJoinedIn(2013);
         }
     
         public CustomerBuilder WithFirstName(string firstName)
         {
             return Set(x => x.FirstName, firstName);
         }
     
         public CustomerBuilder WithLastName(string lastName)
         {
             return Set(x => x.LastName, lastName);
         }
     
         public CustomerBuilder WhoJoinedIn(int yearJoined)
         {
             return Set(x => x.YearJoined, yearJoined);
         }
     
         protected override Customer BuildObject()
         {
             return new Customer(
                 Get(x => x.FirstName),
                 Get(x => x.LastName),
                 Get(x => x.YearJoined)
             );
         }
     }
    
  3. Use the builder in a test, e.g.

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

How can I create a list of entities using my builders?

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):

[
	{
		"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
	}
]

Castle Dynamic Proxy Generator Exception error

If you use the list builder functionality and get the following error:

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.

Then you either need to:

  • Make your builder class public
  • Add the following to your AssemblyInfo.cs file: [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

Create Entities Implicitly

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.

So, to create a single entity:

Customer customer = new CustomerBuilder();

Customer customer = new CustomerBuilder()
    .WithFirstName("Matt")
    .WithLastName("Kocaj")
    .WhoJoinedIn(2010);

Or to create a list of entities:

List<Customer> entities = BasicCustomerBuilder.CreateListOfSize(5);

List<Customer> data = CustomerBuilder.CreateListOfSize(3)
    .All().With(b => b.WithFirstName(generator.Generate().ToString()));

Anonymous Values and Equivalence Classes

todo: Coming soon!

How can I create proxy objects?

This library integrates with NSubstitute 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.

var customer = CustomerBuilder.WithFirstName("Rob").AsProxy().Build();
customer.CustomerForHowManyYears(Arg.Any<DateTime>()).Returns(10);
var name = customer.FirstName; // "Rob"
var years = customer.CustomerForHowManyYears(DateTime.Now); // 10

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.

    class CustomerBuilder : TestDataBuilder<Customer, CustomerBuilder>
    {
        // ...
        
        private int _years;
        
        public CustomerBuilder HasBeenMemberForYears(int years)
        {
            _years = years;
            return this;
        }
        
        protected override void AlterProxy(Customer proxy)
        {
            proxy.CustomerForHowManyYears(Arg.Any<DateTime>()).Returns(_years);
        }
        
        // ...
    }
    
    // Then in your test you can use:
    
    var customer = new CustomerBuilder().AsProxy().HasBeenMemberForYears(10);
    var years = customer.CustomerForHowManyYears(DateTime.Now); // 10

Remember that when using proxy objects of real classes that you need to mark properties and methods as virtual and have a protected empty constructor.

Why does TestStack.Dossier have NSubstitute and AutoFixture as dependencies?

TestStack.Dossier is an opinionated framework and as such prescribes how to build your fixture data, including how to build lists, anonymous data and mock objects. Because of this we have decided to bundle it with the best of breed libraries for this purpose: AutoFixture and NSubstitute.

This allows for this library to provide a rich value-add on top of the basics of tracking properties in a dictionary in the TestDataBuilder base class. If you want to use different libraries or want a cut down version that doesn't come with NSubstitute or AutoFixture and the extra functionality they bring then take the TestDataBuilder.cs file and cut out the bits you don't want - open source ftw :).

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.

Contributions / Questions

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.

About

TestStack.Dossier allows you to quickly, tersely and easily generate fixture data for automated .NET tests.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C# 100.0%