How to write better assertions: 5 Best Practices

Cesar Aguirre - Aug 2 '21 - - Dev Community

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


There's a lot to say about writing good unit tests. This time, let's focus on Assertions. Here you have 5 tips to write better assertions on your tests.

TL;DR

  1. Follow the Arrange/Act/Assert (AAA) pattern
  2. Separate each A of the AAA pattern
  3. Don't put logic in your assertions
  4. Have a single Act and Assert
  5. Use the right Assertion methods

1. Follow the Arrange/Act/Assert (AAA) pattern

If you could take home only one thing: follow the Arrange/Act/Assert (AAA) pattern.

The Arrange/Act/Assert (AAA) pattern states that each test should contain three parts: Arrange, Act and Assert.

In the Arrange part, we create input values and initialize classes to call the entry point of the code under test.

In the Act part, we call the entry point to trigger the logic being tested.

In the Assert part, we verify the expected behavior of the code under test.

For example, let's bring back one test for Stringie, a (fictional) library to manipulate strings, to show the AAA pattern. Notice how each test has these 3 parts.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Stringie.UnitTests
{
    [TestClass]
    public class RemoveTests
    {
        [TestMethod]
        public void Remove_ASubstring_RemovesThatSubstring()
        {
            // Arrange
            string str = "Hello, world!";

            // Act
            string transformed = str.Remove("Hello");

            // Assert
            Assert.AreEqual(", world!", transformed);
        }

        [TestMethod]
        public void Remove_NoParameters_ReturnsEmpty()
        {
            // Arrange
            string str = "Hello, world!";

            // Arrange
            string transformed = str.Remove();

            // Arrange
            Assert.AreEqual(0, transformed.Length);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

For the sake of the example, we have put comments in each part. You don't need to do that on your own tests.

ICYMI, we've been using Stringie to write our first unit tests in C# and to learn about four common mistakes when writing tests.

2. Separate each A of the AAA pattern

Use line breaks to visually separate the AAA parts of each test.

Take a look at the previous tests without line breaks between each AAA part.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Stringie.UnitTests
{
    [TestClass]
    public class RemoveTests
    {
        [TestMethod]
        public void Remove_ASubstring_RemovesThatSubstring()
        {
            string str = "Hello, world!";
            string transformed = str.Remove("Hello");
            Assert.AreEqual(", world!", transformed);
        }

        [TestMethod]
        public void Remove_NoParameters_ReturnsEmpty()
        {
            string str = "Hello, world!";
            string transformed = str.Remove();
            Assert.AreEqual(0, transformed.Length);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Not that bad. The larger the tests, the harder it gets. Have the three AAA parts separated to make your tests easier to read.

In case you're wondering about those weird method names, that's one of four test naming conventions I've found.

3. Don't put logic in your assertions

Don't repeat the logic under test in your assertions. And, please, don't copy the tested logic and paste it into private methods in your test files to use it in your assertions.

Use known or pre-calculated values. Declare constants for common expected values.

[TestMethod]
public void Remove_ASubstring_RemovesThatSubstringFromTheEnd()
{
    string str = "Hello, world! Again, Hello";

    string transformed = str.Remove("Hello").From(The.End);

    // Notice how we hardcode an expected value here
    Assert.AreEqual("Hello, world! Again,", transformed);
}
Enter fullscreen mode Exit fullscreen mode

Notice how we hardcoded the expected value on our previous tests.

4. Have a single Act and Assert

Have a single Act and Assert parts in your tests. Use parameterized tests to test the same scenario with different test values. And, don't put the test values inside an array to loop through it to assert on each value.

This is a parameterized test with MSTest.

[DataTestMethod]
[DataRow("Hello")]
[DataRow("HELLO")]
[DataRow("HeLlo")]
public void Remove_SubstringWithDifferentCase_RemovesSubstring(string substringToRemove)
{
    var str = "Hello, world!";

    var transformed = str.RemoveAll(substringToRemove).IgnoringCase();

    Assert.AreEqual(", world!", transformed);
}
Enter fullscreen mode Exit fullscreen mode

5. Use the right Assertion methods

Use the right assertion methods of your testing framework. And, don't roll your own assertion framework.

For example, prefer Assert.IsNull(result); over Assert.AreEqual(null, result);. And, prefer Assert.IsTrue(result) over Assert.AreEqual(true, result);.

When working with strings, prefer methods from StringAssert class like Contains() and StartsWith() instead of comparing exactly two strings.

Voilà! These are 5 tips to write better assertions. If you want a more complete list of best practices to write your unit tests, check my post Unit Testing Best Practices.


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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .