Meaningful software verification involves comprehensive testing. A recurring task is writing source code that generates or describes test data. Depending on the volume of data, this can be tedious and slow down the development process, and in the worst case, lead to the complete omission of testing. An elegant way to generate test data easily and quickly is by using dummy factories.

Test data

For calling methods from tests, they often need to be fed with defined input parameters. The further down we are in the test pyramid, the more synthetic these data will be. The structure of the data can range from simple primitive data types like strings, booleans, or numerical values to complex object structures.

Let’s take a look at an imaginary e-commerce software with the following data classes. The examples are written in C#, but can be adapted to other programming languages.

public record Address(Guid Id, string Street, string City,
  string State, string ZipCode);

public record Customer(Guid Id, string FirstName, string LastName,
  Address Address, string Email, bool Active);

public record Product(string Sku, string Name,
  string Description, decimal Price);

public record Order(string OrderNumber, DateTime OrderDate,
  Customer Customer, IEnumerable<OrderItem> Items);

public record OrderItem(Product Product, int Quantity, decimal Price);

For a method OrderService.PlaceOrder(..), a unit test could begin something like this:

[Test]
public void Should_Place_Order()
{
    // Arrange
    var address = new Address(Guid.NewGuid(), "123 Main St",
      "Anytown", "TX", "12345");
    var customer = new Customer(Guid.NewGuid(), "John", "Doe", address,
      "john.doe@example.com", true);

    var product1 = new Product("P-001", "Product 1", "Product 1 Description", 9.99m);
    var product2 = new Product("P-002", "Product 2", "Product 2 Description", 19.99m);

    var orderItems = new[]
    {
        new OrderItem(product1, 1, product1.Price),
        new OrderItem(product2, 2, product2.Price)
    };
    var order = new Order("O-001", DateTime.UtcNow, customer, orderItems);

    // Act
    var sut = new OrderService();
    sut.PlaceOrder(order);

    // Assert
    //...
}

However, before we can reach the point of calling our method to be tested with the correct parameters, we need to write several lines of setup code just to create a valid Order. An Order contains a Customer with an Address and a quantity of OrderItems with associated Products.

This code not only becomes quickly redundant but also error-prone. Now, if we imagine that we have to create a new Order for each test method, it becomes evident that we can lose a lot of time here.

Another disadvantage: if there are changes to our data classes, for example, if a Customer now also needs to have a phone number, and we add non-optional parameters, we would have to update the constructor calls in all test methods that create these data instances.

Dummy factories

One solution to these issues is to create dummy factories. These are classes and methods that are available to us in all tests after being written once, making it easier to generate test data. For each data class, a dummy method is created, ideally with default parameters. These parameters can be overridden as needed in the test methods for the specific use case. For nested data structures, nullable reference types can be used, which, in the absence of data, can be replaced with dummy values.

public static class Dummies
{
    public static Address Address(
        Guid? id = null,
        string street = "123 Main St",
        string city = "Anytown",
        string state = "TX",
        string zipCode = "12345") =>
        new(id ?? Guid.NewGuid(), street, city, state, zipCode);

    public static Customer Customer(
        Guid? id = null,
        string firstName = "John",
        string lastName = "Doe",
        Address? address = null,
        string email = "john.doe@example.com",
        bool active = true) =>
        new(id ?? Guid.NewGuid(), firstName, lastName, 
          address ?? Address(), email, active);

    public static Product Product(
        string sku = "P-001",
        string name = "Product 1",
        string description = "Product 1 Description",
        decimal price = 9.99m) =>
        new(sku, name, description, price);
    
    // ...
}

With small helper methods, you can further simplify your life.

public static T[] Many<T>(params T[] items) => items.ToArray();

The test method shown above can now look like this:

using static Company.Webstore.Tests.Dummies;

[Test]
public void Should_Place_Order()
{
    // Arrange
    var orderItems = Many(
        OrderItem(),
        OrderItem(Product(sku: "P-002", price: 19.99m), 2));
    var order = Order(items: orderItems);

    // Act
    var sut = new OrderService();
    sut.PlaceOrder(order);

    // Assert
    //...
}

By using a using static statement, you can call the dummy method directly without having to prepend the class name.

With this, our test setup is reduced to just a few relevant lines.

If there are any changes to our data classes, we only need to adjust the dummy method, and the test methods remain untouched.

Alternatives

To generate more or less random test or fake data, libraries like Bogus or FsCheck are available. Of course, using these libraries involves giving up some control, whereas using dummies is much more explicit. Depending on the use case, this can be an advantage and justify the additional complexity.