Unit Testing – What Should I Test?

This is Part 5 of my series on Automated Testing. Here are links to the previous posts in this series:

In this installment, we’ll look at the what of Unit Testing. While this will soon become natural after a bit of practice, it can be a bit hard when starting off. You may stare off into your lines of operational code and may not know where to start. So, in these sections below, I’ll list out a few things that you can seek out in your operational code and write Unit Tests around. Hopefully, any remaining lines of code will subsequently fall into place with either the tests you have already written or they may become quite apparent after you address these.

Test Return Values

One type of test that you can write for your methods is to test the return value of your method. What does your method return? Is it what you expected? If you refer back to Part 3 (Your First Unit Test) in this series, that is the type of test that we wrote against the Greeter class. We checked to see if the SayHello method return the greeting that we expected it to return:

namespace UnitTestDemoPartOne
{
    public class Greeter
    {
        public string SayHello(string name)
        {
            return $"Hello, {name}";
        }
    }
}
using Xunit;
namespace UnitTestDemoPartOne.UnitTests
{
    public class GreeterTests
    {
        [Fact]
        private void SayHello_WhenCalledWithAName_ReturnsAGreetingWithThatName()
        {
            // Arrange
            var greeter = new Greeter();
            // Act
            var result = greeter.SayHello("Tom");
            // Assert
            Assert.Equal("Hello, Tom", result);
        }
    }
}

Test State Changes

What about the tests that we wrote against the AddTodo method of the TodoListManager class, in the previous post? The AddTodo method did not return any values so instead, we tested the public property within the class, List<string> TodoList, to see if it contained the item that we added. These types of tests can be classified as tests that evaluate changes in state. in this example, the state of the list changed from having no items to having a single item: Get milk.

using System.Collections.Generic;

namespace UnitTestsDemoPart2
{
    public class TodoListManager
    {
        public List<string> TodoList { get; set; } = new List<string>();
        public void AddTodo(string task)
        {
            TodoList.Add(task);
        }
    }
}
using Xunit;

namespace UnitTestsDemoPart2.UnitTests
{
    public class TodoListManagerTests
    {
        [Fact]
        public void AddTodo_WhenCalledWithATodoItem_AddsItemToList()
        {
            // Arrange
            var sut = new TodoListManager();

            // Act
            sut.AddTodo("Get milk");

            // Assert
            var result = Assert.Single(sut.TodoList);
            Assert.Equal("Get milk", result);
        }
    }
}

Test for Exceptions Thrown

Another type of testing that you can do against your methods is to ensure that it throws the exceptions that you intend it to throw at the appropriate situations. xUnit, like all the other major test frameworks has provisions to check for exceptions when executing your operational code. To demonstrate this, let’s look at a modified version of the AddTodo method of our TodoListManager class:

using System.Collections.Generic;

namespace UnitTestsDemoPart3
{
    public class TodoListManager
    {
        public List<string> TodoList { get; set; } = new List<string>();
        public void AddTodo(string task)
        {
            if (string.IsNullOrWhiteSpace(task))
            {
                throw new ArgumentException("Invalid Task");
            }
            
            TodoList.Add(task);
        }
    }
}

In this modified version of the AddTodo method above, I have added a check to see if the incoming string is null or empty. If so, I’m throwing an ArgumentException to let the caller know that they have tried to enter an invalid task to the Todo List. Now, we can test for this scenario, like so:

using Xunit;

namespace UnitTestsDemoPart3.UnitTests
{
    public class TodoListManagerTests
    {
        [Fact]
        public void AddTodo_WhenCalledWithnInvalidTodo_ThrowsArgumentException()
        {
            var sut = new TodoListManager();
            Assert.Throws<ArgumentException>(() => {
                sut.AddTodo(null);
            });
        }
    }
}

Test for Other Method Invocations

Another class of tests that you can write against your operational code is to test for the invocation of other methods from within the method that you are testing. Did it call the other method you expected it to call? Did it call that other method exactly the same number of times you expected? To effectively conduct these types of tests, I’ll first introduce two new things that we haven’t yet looked at in this series of blog posts:

Utilizing C# Interfaces

Like in all the other major Object Oriented Programming (OOP) languages out there, C# too provides an Interface construct that allows you to define the shape of a Class, a contract if you will, and then allow you or other developers using your code to write concrete classes that adhere to that contract. You may have heard someone mention that interfaces help in writing Unit Tests but may not have fully understood how. Today, we’ll look at an example of how introducing interfaces will aid in our testing endeavors. Take a look at this example interface and concrete implementation of it that I have made below.

namespace UnitTestsDemoPart3
{ 
    public interface IEmailer
    {
        public void SendWelcomeEmail(string name);
    }

    public class Emailer : IEmailer
    {
        public void SendWelcomeEmail(string name)
        {
            // send out email here
        }
    }
}

As you can see, it doesn’t do any real work but are just there to get the concepts across. The IEmailer interface has a method named SendWelcomeEmail. Emailer is a concrete class that implements IEmailer.

Next, I have introduced this interface in a modified version of the Greeter class:

namespace UnitTestsDemoPart3
{
    public class Greeter
    {
        private readonly IEmailer emailer;

        public Greeter(IEmailer emailer)
        {
            this.emailer = emailer;
        }

        public string SayHello(string name)
        {
            emailer.SendWelcomeEmail(name);
            return $"Hello, {name}!";
        }
    }
}

In the example Greeter class above, there is a constructor that expects an implementation of the IEmailer interface. The SayHello method calls the SendWelcomeEmail method of IEmailer before returning the greeting as a string. Now, can we write a Unit Test to ensure that the SendWelcomeEmail method gets invoked? Yes, you can probably write something that utilizes Reflection in C# or add some additional publicly accessible properties or methods to infer this call but there is a much simpler or cleaner way to achieve this by using a mocking library.

Mocking Library

Fundamentally, a mocking tool looks at the shape of an object using the interface that it adheres to and creates its own implementation of this interface. For example, we can have the mocking library create its own implementation of the IEmailer interface. The library understands that the object it must create should have a single method called SendWelcomeEmail that must take a string input parameter. When creating this, the library will add many hooks associated with this method so that we can then turn around and ascertain details about how this implementation (and by proxy, how other implementations that use this same interface) is being utilized. It lets us answer questions like:

  • Did a particular method get invoked?
  • How many times did it get invoked?
  • What order did the methods get invoked?
  • What were the values of the parameters that were passed in?

Best of all, we get all this simply by pulling in an external library and not having to hand-code all those extra logic solely for testing purposes. While there are many choices in this area, one that I have personal experience with is a library called Moq. It’s an Open Source project hosted on GitHub, here and one of the favorites among .NET developers. You can pull it into your project from NuGet.

NuGet packages showing Moq

Construct Your Unit-Test to Test for Method Invocation

Now with interfaces and Moq in place, let’s go ahead and write that unit test to assert that the SayHello method of the Greeter class makes a call out to the SendWelcomeEmail method of the IEmailer interface implementation.

using Moq;
using Xunit;

namespace UnitTestsDemoPart3.UnitTests
{
    public class GreeterTests
    {
        [Fact]
        public void SayHello_WhenCalled_CallsTheSendWelcomeEmailMethod()
        {
            var mockEmailer = new Mock<IEmailer>();
            var greeter = new Greeter(mockEmailer.Object);

            // Act
            greeter.SayHello("Tom");

            // Assert
            mockEmailer.Verify(v => v.SendWelcomeEmail("Tom"), Times.Once);
        }
    }
}

The unit test above first creates a mock version of the IEmailer interface. This mock version is then passed to the constructor of the Greeter class to generate a Greeter object. The SayHello method is invoked. Then we assert that this action in turn invoked the SendWelcomeEmail method of the IEmailer implementation, passed in a value of “Tom” as the input parameter and this invocation was done exactly one time.

Test explorer showing the SayHello_WhenCalled_CallsTheSendWelcomeEmailMethod test passing.

I should also make a note of a few things that we shouldn’t write unit tests for:

  • Plain Old CLR Objects (POCOs): If your class is simply a set of properties with no associated business logic, don’t bother testing it.
  • Constructors: If you are simply assigning input parameters to backing fields, don’t bother. If you got some custom validation in there, by all means, test that they work as expected.
  • The Framework: Test your business logic, not the behavior of C# or the .NET Framework.

Closing Remarks

Today we looked at four different types of Unit Tests that we can write against our production / operational code. If you are stuck figuring out what unit tests to write, test these four categories. We also a looked a list of items that we can not bother testing. We also got a very brief introduction to Mocking Libraries, specifically Moq. We’ll have more examples with Moq in a future installment. If you like to play with any of the sample code that I shared in this post, you can clone the companion repository for this post from my GitHub account, here:

tvaidyan/unit-test-demos-part-3: Companion repo to my blog post titled Unit Testing – What Should I Test, on tvaidyan.com (github.com)

Leave a Comment

Your email address will not be published. Required fields are marked *