Learn

An introduction to unit testing in C#

Unit testing has become a must-have skill in modern software development. If you write C# code but don’t know unit testing yet, then this post was tailor-made for you. We’ll start by answering some fundamental questions. Then, we’ll get a bit more specific and practical.

 

First, we’ll cover the main tools and libraries C# developers use for unit testing. Then, we’ll show you how to write your first unit test in C#. Finally, we’ll share crucial unit testing best practices and qualities of great unit tests.

intro-c sharp

Prerequisites

To generally understand the post, we assume you have some programming experience under your belt. Familiarity with C# will make things easier, but even if you only have experience with other languages, you should be fine.

If you want to follow along with the practical bit of the post, you should have:

the .NET SDK installed on your machine (I’m using .NET 7, the most recent stable version at the time of this writing.), and

Visual Studio Code with the C# Dev Kit installed.

A quick disclaimer

There’s no universal agreement on what a unit test is, so for this introductory post, I’ve chosen one definition. While some may disagree, it’s a good starting point for beginners.

Later in the post, I’ll talk about mocks, which is another source of contention. There’s a rich and nuanced vocabulary with which to discuss “fake” implementation of objects to replace dependencies inside tests, such as mocks, stubs, spies, and so on. In my experience, most people use the term “mock” for everything, so that’s the stance I took here. With time, as you gain more experience, you’ll feel more comfortable exploring the different schools of thought around unit testing and mocking.

Without further ado, let’s get started.

C# unit testing: The fundamentals

Let’s begin by covering the what-why-how of unit testing.

Unit testing, in C# or otherwise, is the process of testing an application with automatic tests written in a programming language

What is unit testing in C#

Unit testing, in C# or otherwise, is the process of testing an application with automatic tests written in a programming language. Unit tests are small tests that exercise a small portion of the application code—the so-called “unit”—in isolation, to verify whether it behaves as expected.

What exactly is a unit? Most people who teach unit testing will probably say it’s a method or function, so let’s follow that definition.

Why should you care about unit testing?

Writing and maintaining unit tests takes time and effort. Why should you bother doing so? Here’s a non-exhaustive list of unit testing’s benefits:

  • Fewer bugs. Unit testing allows you to find and fix bugs earlier before they make it to production, increasing code quality.
  • Self-documenting code. If you write them well, tests double as a form of documentation for the code.
  • Easier refactoring. A comprehensive suite of unit tests acts as a safety net, giving you and your coworkers the confidence to make structural changes to the code to improve the application.
  • Cleaner and more concise code. To be able to unit test an application, you need to write it following certain best practices that make the code more modular, less tightly coupled, and easier to read and maintain.

How does unit testing work?

Unit tests are code. Software engineers write them using the same language they use to write the production code. To be able to create unit tests, you need a unit testing framework, such as JUnit for Java, PHPUnit for PHP, and NUnit for C#/.NET (We’ll use this one in a minute.) The unit testing framework provides special classes and methods to express expectations about the code’s behavior.

We usually call these expectations assertions. The unit testing framework is often accompanied by a unit test runner, which is the tool you use to run your tests and automatically assess whether they passed or failed.

It’s common to ensure unit tests—along with other types of automated tests—are added to a CI/CD pipeline. That way, every time a developer pushes new changes to the mainline, the application is tested. If a test breaks the process is interrupted, and the code doesn’t get shipped to production or an intermediary environment.

Let’s now cover what tools you can use to do unit testing in C#.

Unit testing frameworks

The most basic and important tool is, as you’ve seen, a unit testing framework. The main frameworks are:

  • NUnit,
  • xUnit.NET
  • MSTest

Which should you use? I strongly recommend not using MSTest. It’s weak in features compared with the other two, and it’s less used overall. I personally like and use NUnit the most, but it seems nowadays xUnit.NET is what most .NET folks use.

Assertion libraries

Assertion libraries are tools you can use to change the way you write your assertions, with the goal of making tests more readable. These tools are optional, but many testers strongly prefer using them over native assertions.

Here’s an example taken from the readme of one assertion library:

// a traditional assert

Assert.That(contestant.Points, Is.EqualTo(1337));

// an assert using the `Shouldly` library

contestant.Points.ShouldBe(1337);

Some popular assertion libraries for .NET are:

  • Should
  • Shoudly
  • Fluent Assertions

My view on assertion libraries is that, as a beginner, you should initially stay clear of them. First, learn to use the native assertion styles from the framework you’re using. After you’re experienced, and if you feel dissatisfied with the readability of your assertions, then start exploring assertion libraries.

Mocking tools

As per our definition, you’ve seen that a unit test should exercise its unit in isolation. That means the unit being tested shouldn’t talk to external dependencies such as the database, the file system, a RESTful API, or even the system clock.

When you conduct your tests, replace the dependencies with fake substitutes, often called mocks. You can program mocks to respond with predetermined responses, making assertions possible. You can even verify that a given mock was called with the expected arguments and how many times.

The main mocking tools for C#/.NET are:

  • Moq
  • NSubstitute
  • FakeItEasy

Talk is cheap; show me the code: A C# unit testing example

Now, let’s see a quick C# unit testing example. Using your terminal, create a directory and access it:

mkdir unit-testing-example

cd unit-testing example

Then, you’ll create a .NET project of the type class library:

dotnet new classlib -n UnitTestingExample.Application

After that, you’ll add a new project of the type nunit:

dotnet new nunit -n UnitTestingExample.Tests

Now you have two projects: a production one and a test one. The final step is to make your test project able to “see” the production one by adding a reference:

dotnet add UnitTestingExample.Tests\UnitTestingExample.Tests.csproj reference UnitTestingExample.Application\UnitTestingExample.Application.csproj

Finally, open everything using VS Code:

code .

With the project open, find the class Class1.cs. Rename it to Author.cs, then replace its contents with the following:
namespace UnitTestingExample.Application;

public class Author

{

private readonly string _fullName;

public Author(string fullName)

{

_fullName = fullName;

}

public string Authorify()

{

var parts = _fullName.Split(" ");

var surname = parts[^1].ToUpper();

var remaining = string.Join(

" ",

parts.SkipLast(1).Select(x => $"{x[0]}."));

return $"{surname}, {remaining}";

}

}

The class above represents an author—think of a system for a library or bookshop. The constructor takes the full name as an argument and uses the Authorify method to display the name in a specific format. For example, “Hommer Jay Simpson” would be converted to “SIMPSON, H. J.”

You’re now ready to write your first unit test!

On VS Code, locate the class UnitTest1.cs. Rename it to Author.Test.cs, and replace its contents with the following:

using UnitTestingExample.Application;

namespace UnitTestingExample.Tests;

public class AuthorTest

{

[Test]

public void Authorify_AuthorWithNoMiddleName_ReturnsAuthorNameWithOneInitial()

{

// arrange

var sut = new Author("Albert Einstein");

// act

string result = sut.Authorify();

// assert

var expected = "EINSTEIN, A.";

Assert.AreEqual(expected, result);

}

}

This is your first unit test. It has an unusually long name, but that’s by design: I’m following Roy Osherove’s naming standards for unit tests:

  • the first part of the name specifies which method we’re testing

Example: Authorify

  • the second part of the name specifies which scenario we’re testing — for example, an author without a middle name

Example: AuthorWithNoMiddleName

  • the last part of the name expresses the expected result

Example: ReturnsAuthorNameWithOneInitial

So, the full name of the unit test Authorify_AuthorWithNoMiddleName_ReturnsAuthorNameWithOneInitial indicates that we are testing the Authorify method with the scenario of an author without a middle name, and we expect the result to be the author’s name formatted with one initial.

As you can see, the test contains three distinct phases.

  1. First, we arrange or prepare the objects for testing.
  2. Then, we act—that is, we make the object perform the behavior we want to test.
  3. Finally, we assert—that is, we verify whether the result we got is the result we expected.

We are using the AreEqual method on the class Assert to verify whether the result we got is indeed equal to “EINSTEIN, A.”

Let’s now run the test. First, you need to build your test project. If you have the C# Dev Kit extension installed, you should have the Solution Explorer available on your VS Code. Go there, find your test project, right-click on it, and click on build:

 Unit Testing in C#

After that, you can click on the testing icon on the left panel (1), then click on the “play” sign next to the test project name (2):

 Unit Testing in c#

If you did everything right, the screen will indicate that the test has passed:

Unit Testing

Success! You wrote a passing C# unit test.

As an exercise for you, try to write a test to verify whether an author with a middle name also wields the expected result. For instance, “Hans Albert Einstein” should wield “EINSTEIN, H. A.”

Don’t put logic inside your tests, such as if statements and for loops

What are the best practices for unit testing in .NET?

Make no mistake: your first unit tests will likely be … not so great, to put it mildly. Writing readable and maintainable tests is a skill that takes years of practice to master.

However, there are best practices and guidelines that will start you on the right path:

  • Make your tests super simple. Don’t put logic inside your tests, such as if statements and for loops. Keeping tests as simple as possible helps prevent the introduction of bugs.
  • Follow a naming convention for your tests. In the example above, I used Roy Osherove’s convention, but that’s not the only one available.
  • Make your tests independent of one another. Unit tests should never rely on each other. You should be able to run them in any order or even simultaneously. Don’t make your tests share data between them.
  • Follow the A-A-A structure. As in the example above, follow the Arrange-Act-Assert structure in your tests to ensure they’re readable.

There are many more best practices and guidelines, but these are the main ones you need to be aware of when starting your C# unit testing journey.

Start testing to be a happier developer!

Long gone are the days when software testing was a concern solely for the QA department. Nowadays, software engineers are expected to test the software they write and ship—manually, if it makes sense to do so, but for the most part, using automated testing.

In this post, we’ve treated you to a C# unit testing introduction. You’ve learned what unit testing is, why it matters, and how to get started with it in C#.

Before parting ways, I recommend two books for you to continue learning. The first is “The Art of Unit Testing, With Examples in C#” by Roy Osherove. It’s a practical book full of examples, best practices, anti-patterns to avoid, and a discussion of tools.

The other one is “xUnit Test Patterns: Refactoring Test Code” by Gerard Meszaros. As the name implies, this book is a collection of patterns for test code. It’s not necessarily meant to be read back-to-back with the other book; instead, it’s a reference or catalog of useful patterns you can refer to when writing unit tests.

This post was written by Carlos Schults. Carlos is a consultant and software engineer with experience in desktop, web, and mobile development. Though his primary language is C#, he has experience with several languages and platforms. His main interests include automated testing, version control, and code quality.

Author:

Tricentis Staff

Various contributors

Date: Apr. 01, 2024