Unit testing best practices with .NET

Poatek
Poatek
Published in
5 min readOct 18, 2022

How many times have you joined a project and the project didn’t have unit tests, or some of the tests were failing because of missing configuration, or there was a mix of test types, and you couldn’t tell which were unit and which were integration tests? Indeed, this happens to me a lot. This post will present some benefits of unit testing and best practices for writing good tests and organizing them in your project.

First of all, let's learn what a good unit test must have

  • It needs to be extremely fast:
  • Developers write unit tests to run them repeatedly and check that no bugs have been introduced.
  • Slow unit tests can discourage developers from running them due to loss of productivity. So avoid thread sleep, task.delay, timeouts, etc.
  • It needs to focus on a single unit, not on integrating multiple units.
  • It must test a remote unit, for example, a class, meaning you need to mock all the dependencies, eliminating the influence of external factors.
  • It needs to be readable.
  • The intent of a unit test should be clear. A good unit test tells a story about some behavioral aspect of the application, so it should be easy to understand which scenario is being tested.
  • Always use coding best practices to easily create test scenarios.
  • It needs to be reliable**.**
  • Unit tests should only fail if there’s a bug in the system under test. It should be reproducible and independent of external factors such as the environment or running order.

Benefits of unit testing

  • It will show how poorly-designed your code is, raising several red flags about the implementation.
  • Big unit test classes signal your code’s complexity: maybe it is doing more than necessary. Are you following the Simple Responsibility Principle?
  • Do you need to create a big setup before testing? Why do you have so many dependencies for just a unit?
  • Avoid making unit tests after the fact once the logic has merged. Instead, strive to do it as a single task, preferably in the same PR. This single task allows you to leverage the tests’ information to improve the code before it is finished.
  • It makes it safer to refactor;
  • You don’t have to worry about corner cases or side effects if unit tests cover your changing code. If you inadvertently introduce a bug, existing tests should catch it.
  • It is fast and can be easily integrated into the CI/CD pipeline;
  • It can be used as documentation;
  • Well-written tests serve as documentation for different functionality of a class: each test modeling a single scenario or capability;
  • No particular configuration is needed; no influence from external factors;
  • Unit tests will hardly ever need external configuration, unlike integration tests which rely on database connection strings, service endpoint addresses, etc.

Creating a unit test project

The idea is to isolate the projects, so we’ll end up with one Unit Test project per Real Project in your codebase. There are several reasons for that:

  • Facilitate the navigation by having the same structure(namespace) as the actual project;
  • Reduce project dependencies;
  • Isolate the different types of tests (Unit, Integration).

Nuget packages

I recommend 3 NuGet packages to get started:

  • MSTest: MSTest is the default test framework shipped along with Visual Studio. I always use the default before going to a non-default solution such as XUnit and NUnit.
  • NSubstitute: As we mock all the dependencies, NSubstitute has been a good fit for me, and the documentation is pretty good. You can find it here.
  • FluentAssertions(Optional): “With Fluent Assertions, the assertions look beautiful, natural and, most importantly, extremely readable” — Girish

Unit test project

Use the same name as the actual project’s name, with the ‘.UnitTests’ suffix:

  • {projectName}.UnitTests

So if you have the following projects:

  • Microsoft.Office.PowerPoint
  • Microsoft.Office.Word

You will have:

  • Microsoft.Office.PowerPoint
  • Microsoft.Office.PowerPoint.UnitTests
  • Microsoft.Office.Word
  • Microsoft.Office.Word.UnitTests

Why not use the 'test' suffix?

Using a more specific suffix avoids mixing types of tests in the same project. For example, you might have integration and unit tests covering similar parts of the codebase. Still, you want to have finer control over the total code coverage percentage for each test type separately: integration tests will usually cover more codebase with fewer lines of code than unit tests, which are more focused.

Namespace

Maintain the same structure as the actual project. So, change the namespace to match it. This structure will bring an easier class discovery and navigation.

Unit Test Class

You need a test class to encapsulate all your test cases. The name should be the same as the actual class project with the ‘Tests’ suffix and namespace. These tests are a convention used in .NET and facilitate discovery and navigation.

Unit Test Method

As mentioned before, the intent of a unit test should be clear. A good method name should make it easy to understand which scenario is being tested. So the title should contain three parts:

  1. What you are doing or the name of the method being tested.
  2. The scenario under which the action is being performed (can be omitted for unconditional tests);
  3. The expected behavior.

So, it will be:

{DoingSomething}{StateUnderTest}{ExpectedBehavior}

Example:

public void DoingSomethingWithValidParameterShouldReturnExpectedValue()
{

var someValue = DoSomething("validParameter");

someValue.Should().Be(expectedValue);
}
// or
public void DoSomething_WithValidParameter_ShouldReturnExpectedValue()
{

}

Arrange/Act/Assert

Arrange/Act/Assert(AAA) is a pattern for arranging and formatting code in Unit Test methods.

  • Arrange — set up the testing objects and prepare the prerequisites for your test.
  • Act — perform the actual work of the test.
  • Assert — verify the result.

Code Style

  • Add comments for each A;
  • Let an extra blank line separate them.

Example:

// Arrange
const string expectedValue = "expectedValue";
const string validParameter ="validParameter";
var service = new Service();
// Act
var someValue = service.DoSomething(validParameter);
// Assert
someValue.Should().Be(expectedValue);

Final considerations

After all these tips and recommendations, you should be able to organize your Unit Test projects better and avoid the problems mentioned at the beginning of the post. Hopefully, this article was able to help you realize the value of having unit tests, and you’ll willingly create them instead of being forced to do so as part of some process. As you can see, there are a lot of benefits and good practices that you can start doing in your daily work. These are just best practices and tips to create a good unit test for your project but keep in mind that you do have to know the coding best practices to avoid poorly-designed and untestable code.

Leandro Pereira

--

--

Poatek
Poatek
Editor for

We’re a software engineering company filled with the best tech talent!📍Porto Alegre, São Paulo, Miami and Lisbon linktr.ee/poatek.official