Generating Test Data with AutoPoco

I often run into a scenario where I want to use test data – either for unit testing, integration testing, or simply to have some data for manually testing an application.  Writing classes to generate this testing data can be rather time-consuming, so when someone on twitter mentioned AutoPoco I thought I would take a look.

What’s AutoPoco?

AutoPoco replaces manually written object mothers/test data builders with a fluent interface and an easy way to generate a large amount of readable test data. By default, no manual set-up is required, conventions can then be written against the names/types of property or manual configuration can be used against specific objects.

That’s the official description from the CodePlex page.  That sounds great, but how does it work in practice?

A Quick Example

Installation is quick and easy – you download a single dll which you then need to reference in your project.

I wanted to generate a number of products and have AutoPoco populate the properties with random data.  Using one of the examples in the documentation, I came up with the following.

public enum ProductCategory
{
    Books,
    Music,
    Movies,
    Games
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public ProductCategory Category { get; set; }
    public decimal Price { get; set; }

    public string FormattedPrice
    {
        get { return string.Format("${0:n2}", Price); }
    }
}
public class ProductRepository
{
    private static readonly IList<Product> products;

    static ProductRepository()
    {
        var factory = AutoPocoContainer.Configure(x =>
        {
            x.Conventions(c => c.UseDefaultConventions());
            x.AddFromAssemblyContainingType<Product>();
        });

        var session = factory.CreateSession();
        products = session.List<Product>(50).Get();
    }

    public IQueryable<Product> GetProducts()
    {
        return products.AsQueryable();
    }
}

Right, so when I run this I get a message saying ‘Property set method not found’.  Not a great start – I assumed I am missing something – I can’t imagine that the code simply assumes that all properties are writeable.  However, when I add an empty set method to my FormattedPrice property the error goes away.

Update:  I downloaded and compiled the latest source and this bug has been fixed, although there is no release for it yet.

Right, so the code runs, but what is the output?  It did indeed generate 50 products, and all of them have all their properties set to the default values – the equivalent of new Product()

DataSources

To have AutoPoco populate the properties on our objects we need to do a little extra setup work.  Let’s start by generating Id’s for all the products.

var factory = AutoPocoContainer.Configure(x =>
{
    x.Conventions(c => c.UseDefaultConventions());
    x.AddFromAssemblyContainingType<Product>();
    x.Include<Product>().Setup(p => p.Id).Use<IntegerIdSource>();
});

Now all our objects have been assigned an Id from 0 to 49.  So in this case I’m using a built-in DataSource to specify how the Id property should be populated.  AutoPoco ships with a few built-in DataSources, you can see a complete list here.

Right, so what if you want to create your own DataSource?  Let’s say we want the price to be a random decimal number, and in our setup we would like to be able to specify a maximum value for the price.

public class RandomPriceSource : DatasourceBase<decimal>
{
    private readonly decimal min;
    private readonly decimal max;
    private readonly Random random = new Random();

    public RandomPriceSource(decimal max) : this(0m, max)
    {
    }

    public RandomPriceSource(decimal min, decimal max)
    {
        this.min = min;
        this.max = max;
    }

    public override decimal Next(IGenerationSession session)
    {
        var randomValue = random.NextDouble();
        var stretched = (max - min)*(decimal) randomValue;

        return stretched - min;
    }
}

Easy as pie.  Now to use our new DataSource we simply have to specify it in the setup.

x.Include<Product>().Setup(p => p.Price).Use<RandomPriceSource>(250m);

Now every product is automatically assigned a random price between 0 and 250.  In the end my products were generated by a combination of custom and built-in DataSources.

var factory = AutoPocoContainer.Configure(x =>
{
    x.Conventions(c => c.UseDefaultConventions());
    x.AddFromAssemblyContainingType<Product>();
    x.Include<Product>().Setup(p => p.Id).Use<IntegerIdSource>();
    x.Include<Product>().Setup(p => p.Name).Use<ProductNameSource>();
    x.Include<Product>().Setup(p => p.Description).Use<LoremIpsumSource>();
    x.Include<Product>().Setup(p => p.Price).Use<RandomPriceSource>(250m);
    x.Include<Product>().Setup(p => p.Category).Use<RandomCategorySource>();
});

Conventions

If you’re generating various different types of objects you probably don’t want to have to specify the DataSource for every single property on every single object.  To ease the pain AutoPoco allows you to create Conventions – this is basically a rule that is applied to any property that matches certain criteria.

For example, let’s create a convention that any property with the name Id and the type Integer should be populated by the IntegerIdSource DataSource.

public class IdPropertyConvention : ITypePropertyConvention
{
    public void Apply(ITypePropertyConventionContext context)
    {
        context.SetSource<IntegerIdSource>();
    }

    public void SpecifyRequirements(ITypeMemberConventionRequirements requirements)
    {
        requirements.Name(x => x.Equals("Id"));
        requirements.Type(x => x == typeof(int));
    }
}

Now we simply need to register this convention in our setup.

x.Conventions(c => c.Register(typeof(IdPropertyConvention)));

This yields exactly the same result, but we don’t need to explicitly do the setup for our Id property anymore.  Pretty neat.

Conclusion

AutoPoco is a really useful little tool for generating test data.  There might be a few niggles and the documentation could probably be better, but it’s reasonably straightforward to get it up and running.  In any case, it’s open source so it will get even better over time.  I’m definitely going to try and contribute to this project.

Happy coding.