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 instead.
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 upgrade your unit testing skills, check my course: Mastering C# Unit Testing with Real-world Examples on Udemy. Write readable and maintainable unit tests in 1 hour by refactoring real-world tests — Yes, real tests. No boring tests for a Calculator class.
Happy testing!
Top comments (0)