How to create test data with Object Mothers

I originally posted an extended version of this post on my blog a couple of weeks ago. It's part of a series I've been publishing, called "Unit Testing 101"

One single tip to write better tests is to reduce noise inside our tests. For this, we can use builder methods. They help us to simplify complex Arrange parts or setup scenarios of our tests.

This time, let's use Object Mothers to reduce noise inside our unit tests when creating test data.

Without Object Mothers

Let's validate credit cards. We will use the FluentValidation library to create a validator class. We want to check if a credit card is expired or not. We can write tests like these ones.

using FluentValidation.TestHelper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace UsingObjectMothers
{
    [TestClass]
    public class CreditCardValidationTests
    {
        [TestMethod]
        public void CreditCard_ExpiredYear_ReturnsInvalid()
        {
            var validator = new CreditCardValidator();

            var creditCard = new CreditCard
            {
                CardNumber = "4242424242424242",
                ExpirationYear = DateTime.Now.AddYears(-1).Year,
                ExpirationMonth = DateTime.Now.Month,
                Cvv = 123
            };
            var result = validator.TestValidate(creditCard);

            result.ShouldHaveAnyValidationError();
        }

        [TestMethod]
        public void CreditCard_ExpiredMonth_ReturnsInvalid()
        {
            var validator = new CreditCardValidator();

            var creditCard = new CreditCard
            {
                CardNumber = "4242424242424242",
                ExpirationYear = DateTime.Now.Year,
                ExpirationMonth = DateTime.Now.AddMonths(-1).Month,
                Cvv = 123
            };
            var result = validator.TestValidate(creditCard);

            result.ShouldHaveAnyValidationError();
        }
    }
}

In these tests, we used the TestValidate() and ShouldHaveAnyValidationError() methods from FluentValidation to write better assertions.

In each test, we created a CreditCard object and modified one single property for the given scenario. We had duplication and magic values when initializing the CreditCard object.

Object mothers

In our tests, we should give enough details to our readers, but not too many details to make our tests noisy. We should keep the details at the right level.

In our previous tests, we only cared for the expiration year and month in each test. We can abstract the creation of the CreditCard objects to avoid repetition.

One alternative to abstract the creation of CreditCard objects is to use an object mother.

An object mother is a factory method or property holding a ready-to-use input object. Inside each test, the properties of this object are updated to match the scenario under test.

For our example, we can create a CreditCard property with valid defaults and tweak it inside each test.

Our tests with an object mother for credit cards will look like this.

[TestClass]
public class CreditCardValidationTests
{
    [TestMethod]
    public void CreditCard_ExpiredYear_ReturnsInvalid()
    {
        var validator = new CreditCardValidator();

        // Instead of creating a new card object each time,
        // we rely on this new CreditCard property
        var request = CreditCard;
        request.ExpirationYear = DateTime.Now.AddYears(-1).Year;
        var result = validator.TestValidate(request);

        result.ShouldHaveAnyValidationError();
    }

    [TestMethod]
    public void CreditCard_ExpiredMonth_ReturnsInvalid()
    {
        var validator = new CreditCardValidator();

        var request = CreditCard;
        request.ExpirationMonth = DateTime.Now.AddMonths(-1).Month;
        var result = validator.TestValidate(request);

        result.ShouldHaveAnyValidationError();
    }

    // We have this new property to hold a valid credit card
    private CreditCard CreditCard
        => new CreditCard
        {
            CardNumber = "4242424242424242",
            ExpirationYear = DateTime.Now.Year,
            ExpirationMonth = DateTime.Now.Month,
            Cvv = 123
        };
}

Notice the CreditCard property in our test class and how we update its values from test to test.

Voilà! That's how we can use object mothers to create test data and simplify our tests. Simple trick, right?

Object mothers are fine if you don't have lots of variations of the object being constructed. But, if you do, use builders. To learn about builders, check my post about the Builder pattern.

Remember, in your tests you should give enough details to your readers, but not too many to make your tests noisy. Don't bore your test readers, but don't surprise them either.

If you want to practice writing unit tests, check my Unit Testing 101 repository.

GitHub logo canro91 / Testing101

Unit Testing Workshop for Beginners

If you're new to unit testing or want to learn more, stay tune to my "Unit Testing 101" series on my blog.

Happy testing!

20