Unit Testing in C#: A Surprisingly Simple Way to Write Better Apps


Have you ever written a feature that didn’t work from the start?

The one that had bugs in it?

If so, you know how frustrating it can be to fix bugs.

Writing good quality software isn’t easy, and sometimes things go wrong no matter how hard you try! But with proper planning and execution, you can write better code using unit tests.

Unit testing is a process of verifying that individual units of code are functioning as expected. By unit testing your code, you can verify that each code unit is working correctly before moving on to the next unit.

So if you want your apps running smoothly from start to finish, keep reading this article.

It will walk you through the process of writing unit testing in C# and why they’re so crucial for your codebase. In addition, it’s full of great advice about best practices on writing good quality software.

What is unit testing?

Unit testing is a process of verifying that individual units of code (methods, classes, etc.) work as intended. You can be confident that your code works as expected by writing unit tests and then running them as part of your build process.

By running unit tests on all new and changed code, these units will continue to work as intended. Any errors will be caught and fixed before they cause problems in the production. This saves time and ensures higher quality and reliability in your software.

unit testing

Unit testing is a white-box testing technique. That means a developer has access to the source code under test during testing.

Unit testing is an essential part of any software development process. You should use it in conjunction with other quality assurance measures such as code review, writing documentation, continuous integration, and continuous delivery.

Why is unit testing important?

Unit testing is one of the rare skills you can learn once and benefit from forever. That’s because it is an integral part of writing applications.

As a result, you can use this skill in almost any project.

Writing unit tests is important for several reasons.

Firstly, unit testing makes code more reliable by ensuring that each code unit works as intended. If any unit fails, it can be fixed immediately instead of waiting until the next release. This saves time in the development phase and means you have much less work to do in future updates or bug fixes because you have already found errors in your unit testing phase!

Secondly, unit testing encourages developers to write better quality software because they must think about how their units are tested when writing the code itself. By applying good software practices, you easily extend and maintain the code over time.

Lastly, unit testing gives both users and developers a tool to evaluate new features and code changes. This is especially important for open source projects where new features and code changes are often made available to users for feedback before a formal release.

Unit testing is one of the critical practices of agile development. It’s a practice that helps ensure the quality of the codebase and aids in catching defects early.

The agile philosophy is all about responding to change quickly, and unit testing is a key part of that. By having a suite of automated unit tests, you can be confident that any changes you make to the code won’t break existing functionality. This allows you to make changes quickly and safely without worrying about breaking things.

What are the advantages of unit testing?

Unit testing has several advantages over other types of testing:

  • It helps you write better code.
  • It helps you catch bugs earlier.
  • It helps you detect regression bugs.
  • It makes your code easy to refactor.
  • It makes you more efficient at writing code.

What are the disadvantages of unit testing?

However, unit testing also has some disadvantages:

  • It takes time to write test cases.
  • It’s difficult to write tests for legacy code.
  • Tests require a lot of time for maintenance.
  • It can be challenging to test GUI code.
  • Unit testing can’t catch all errors.

Nevertheless, the advantages of unit testing far outweigh the disadvantages.

Structure of a unit test – AAA in unit testing

arrange-act-assert

Most unit testing frameworks follow the AAA pattern:

  1. Arrange: Set up the test data and create the objects you need for the test.
  2. Act: Execute the code you want to test.
  3. Assert: Check that the results are what you expect.

For example, if you want to test a method that calculates the perimeter of a rectangle, you might arrange in the unit test to set up a rectangle with an X value of 3 and a Y value of 4. The unit test would then act or execute the code that calculates the perimeter by calling the appropriate method in the code. The formula is 2*(X+Y).

In the last step, after you get the result, you would assert to make sure that the correct answer was returned (in this case, 14).

This paragraph barely scratched the surface of this topic. You can find out more about the importance of AAA in unit testing in a separate article. Also, you need to consider whether or not to include multiple asserts in your test method.

How to perform unit testing?

Unit testing can be as simple or complex as you want it to be. The most basic unit test verifies that the code behaves as expected when given specific inputs. 

For example, you might have a unit test that checks if a function returns the correct value when given an input string. More complex unit tests can verify that several code units work together as intended.

What is a unit testing framework?

To unit test your code, you need a unit testing framework. A unit testing framework is a library of classes and methods that help you write unit tests. There are many unit testing frameworks available, but the most popular ones for C# development are:

  • xUnit  – xUnit is a unit test framework for C#. It’s open-source, and you can use it on any platform that supports .NET.
  • NUnit – NUnit test framework is another popular option for C# development. It’s open-source and has many features that make it easier to write unit tests.
  • MS Test – MS Test is the unit testing framework that Microsoft created. It’s popular because it’s easy to use and integrates well with Visual Studio.

Each of these frameworks has its strengths and weaknesses, but you can hardly go wrong if you pick any one of the above.

They are all pretty similar, but I prefer xUnit.

Steps to add tests to the existing solution

To add a new xUnit test project to your existing solution, right-click on the Solution, Add -> New Project…

Add new project

Then, select the testing framework project template.

Create a new c# unit testing project

After creating a unit test project, add a reference to the project you would like to test. That project will be a test target.

Typically unit test classes are written in a separate project to ensure that unit tests are independent of any dependencies or side effects that other components may cause.

Unit testing in C# tutorial

Let’s go through one example to see these main parts in a single unit test. We have a simple Calculator class, which has one method, AddTwoNumbers.

public class Calculator
{
    public int AddTwoNumbers(int first, int second)
    {
        return first + second;
    }
}

(At this point, you are probably wondering why I’m showing you this simple example since the production code is never that simple. Maybe your codebase is a beast called legacy code. But I’m showing you how to test this simple code so we can entirely focus on the mechanics of writing unit tests and put the actual code in the second place.

Plus, you never know. Maybe you will have to test the exact same function in the future. In that case, lucky you…)

Your first unit test

A simple test to check that AddTwoNumbers returns the sum of numbers passed in as parameters might look like:

[Fact]
public void AddTwoNumbers_returns_sum_of_numbers()
{
    //Arrange
    var calculator = new Calculator();
    //Act
    var result = calculator.AddTwoNumbers(5, 6);
    //Assert
    Assert.Equal(11, result);
}
  1. In the Arrange part of the unit test, you initialize the class you want to test.
  2. Next, in the Act part of the unit test, you call the method which is being tested.
  3. In the Assert part of the unit test, you check that the result is as expected. You use Assert.Equal method to do that. The Assert class is a part of a xUnit framework, which provides you with the methods to make writing tests easier and faster.

More advanced example

Ok, the previous example was a simple way to show the elements of a unit test. Next, let’s go a bit further and see how to test a more advanced method.

Create a class called Rocket, which you will test:

public class Rocket
{
    public int Speed { get; set; } = 0;
    public bool EnginesAreWorking { get; set; }
    public string Direction { get; set; } = "No direction...";
 
    public void FlyToTheMoon()
    {
        Speed = 29000;
        EnginesAreWorking = true;
        Direction = "To the Moon!";
    }
}

Please note that the Rocket class has to be declared as public to be visible in the testing project.

Now you create a unit test to check that when you launch the rocket to the Moon, it reaches the desired speed, engines are working, and the direction is correct.

Create a new class and name it RocketTests. This class will be a test file that will contain all the test code for the Rocket class.

The naming convention is ClassName + Tests(ending with plural Tests, since every test class usually contains more than one test).

The test for the FlyToTheMoon method looks like this:

public class RocketTests
{
    [Fact] // In xUnit, the method has to be public and have Fact attribute.
    public void FlyToTheMoon_goes_to_the_moon()
    {
        var rocket = new Rocket();
 
        rocket.FlyToTheMoon();
 
        Assert.True(rocket.EnginesAreWorking);
        Assert.Equal(29000, rocket.Speed);
        Assert.Equal("To the Moon!", rocket.Direction);
    }
}

In xUnit, the method has to be public and have the Fact attribute, and the class has to be public to be recognized as a test method.

In the Arrange step of the test, initialize the Rocket class. Then you execute the FlyToTheMoon method in the Act step. Finally, in the Assert step, the test checks that the properties Speed, EnginesAreWorking, and Direction are set to the expected values.

How to run unit tests in Visual Studio?

You can run tests by:

  • Hitting the Ctrl+R,A keyboard shortcut
  • Through the Test Explorer window, go to the Test menu bar and select the Test Explorer window. Test Explorer window is handy if you want to filter your test suite, aka your collection of tests, by some criteria. You could show only failed tests or only tests covering one module of the system.
  • Or right-click on the test or test class you want to run, and select Run Test(s)
How to run tests in Visual Studio

What this will do, is to invoke a test runner. A test runner is a tool that is a part of the test framework. It will scan all test methods and execute them within the same test run.

Unit testing in isolation

You may have heard that unit testing is the key to writing good code.

But did you know it’s also vital for keeping your tests isolated? That means ensuring each test verifies an aspect of functionality without collaborating with the external environment.

The external environment can be:

If there are collaborations or shared states across multiple tests, things can get complicated, which leads me to my next point…

Test isolation is key to avoid outside factors affecting our unit test results and causing false positives or negatives.

How to achieve test isolation?

One way to achieve unit test isolation is using a mocking framework. The mocking framework is a library that helps you create fake objects and simulate their behavior. It allows you to replace the dependencies of your unit tests with mock objects, which gives you more control over the test environment.

Many mocking frameworks are available for .NET, but one of the most popular is Moq. It’s very powerful, and you can create different test double objects.

What are test doubles?

A test double is a technique used in unit testing to substitute a dependency of the system under test (SUT) with a fake object, allowing you to focus on the behavior of the SUT.

Here are some examples:

  • In the production code without mocks, the class will use a database manager class to access the actual database to read and write data. When writing a test for this class, you may want to use a test double to inject a fake database manager class into the unit test. This will allow you to run your unit tests without any dependencies on the live database, which can be slow and cause your unit tests to fail if the database is unavailable.
  • If you have unit tests that make network calls, you can use a test double to simulate a network connection. This will allow you to run your unit tests in a faster and more reliable environment.

The key to using test doubles in your code is dependency injection. So, if you have trouble injecting fake dependencies in the classes you want to test, tests tell you that something is not right with your code.

There are many different types of test doubles, but they can be divided into two categories: stubs and mocks.

What is a stub, and what is a mock?

A stub is a type of dummy object that you can use to replace a dependency in your unit tests. It has no functionality but allows you to control the return values and side effects of the methods it replaces.

A mock is also a dummy object, but it has more functionality than a stub. In addition to exposing the same APIs as the actual object, a mock can set some expectations on how the class under test should behave in unit tests. If the unit tests are run, and all of these expectations are met, then your unit test is successful.

This topic is so much bigger than you could imagine, so read more about it.

How to name unit tests?

How to name tests?

Use UnitOfWork_ExpectedBehavior_ScenarioUnderTest naming convention.

UnitOfWork name could be as simple as a method name (if that’s the whole unit of work) or more abstract if it’s a use case that encompasses multiple methods or classes such as UserLogin or RemoveUser.

ExpectedBehavior is the result we expect to occur after executing the unit of work.

ScenarioUnderTest represents a scenario or circumstances under which the unit of work is being executed.

What are the best practices for naming unit test classes and test projects? Should the name of the test include the name of the method? How not to name a test method?

Answers to the above questions and much more you can find out in a separate detailed article.

Who performs unit testing and when?

Developers who wrote the code should write the unit tests.

You can run unit tests at any time, but it’s best to run them as often as possible, preferably after every change to the code. This is because they are precious in the early stages of software development.

Some developers find that unit testing is hard. It is hard if the code is not testable or if unit testing is done incorrectly. Unit tests that aren’t written correctly are a waste of time. They won’t help you find and fix errors in your code but will cause a developer to lose trust in unit tests altogether.

Test-first approach – Test driven development (TDD)

test-driven development (tdd)

The test-first approach, also known as test-driven development (TDD), is a software development process that relies on unit testing to ensure the quality of the code. In this process, you write unit test cases before the actual code. After that, you write the code only to make the unit tests pass.

This approach can be challenging because it forces you to think about the public API of the feature before writing any code. However, it can also help prevent you from writing more code than is necessary.

The test-first approach is most effective with the mocking framework and dependency injection.

The TDD approach is an integral part of the agile methodology. It helps fill gaps in your project management plan by providing test cases built before coding begins, ensuring features will work as they should when you finally launch them on live servers.

You can use acceptance test-driven development (ATDD) to develop your application at a higher level.

Unit testing tools

You need good unit testing tools to ensure your code is working correctly.

The unit testing tools you need to consider are:

  • xUnit – unit testing framework
  • Moq – mocking framework
  • FluentAssertions – assertions framework
  • Autofixture – library for generating test data
  • OpenCover – code coverage tool

While not all of them are essential in writing tests, they can make the whole process easier and more effective.

Unit testing best practices

The importance of unit testing cannot be overstated.

It’s one thing to have a few tests here and there. But do you want your application codebase to remain maintainable over time? Then you need good quality tests!

The top 10 unit testing best practices are:

  1. Write simple tests.
  2. Write fast tests.
  3. Test one scenario per test.
  4. Name your tests correctly.
  5. Don’t duplicate production logic.
  6. Write isolated tests.
  7. Don’t overuse mocks and stubs.
  8. Execute test suite often and automatically.
  9. Avoid test interdependence.
  10. Only test the public behavior.

So don’t fall behind when it comes to unit testing! Check out the article on the importance of following these practices.

Unit testing vs other types of testing/testing pyramid

Will unit tests magically solve all your problems?

No. Unit testing is not a silver bullet.

Unit tests only test the functionality of the small, independent pieces of your code. Therefore, they will not catch integration errors, such as features that need multiple units to be executed to work correctly. That’s why you should also perform integration testing and UI testing.

The other thing is that developers need to invest (yes, invest, since that time is repaid in the long term) extra time to write and maintain unit tests.

This is not an easy task, sometimes. We all have deadlines to meet, code reviews to perform, meetings to attend.

Testing pyramid

The testing pyramid is a concept that helps you to understand the different types of testing and how much effort you should put into each type.

In total, there are three layers in this pyramid.

testing pyramid
Testing pyramid

Unit testing occupies the bottom of the pyramid because it is the most important and has the greatest return on investment. Integration Testing is done next because it catches errors that unit tests might not find. Finally, UI Testing is done last because it is the most expensive in terms of time and money.

Unit tests vs integration tests

Unit testing focuses on testing separate classes or methods in isolation from the dependencies. On the other hand, an integration test checks how various units work together.

It is crucial to do unit and integration testing because unit tests will not find all errors. For example, a unit test might pass even if the database is not connected correctly.

In short, unit and integration tests are helpful for any non-perfect application.

Unit tests vs functional tests

Functional tests are more comprehensive than unit tests.

They verify the application’s functionality and ensure it does what it says on paper. Functional tests are also referred to as acceptance tests because they mimic the real-life usage of the application.

Did you know that functional tests are an essential part of software development? It’s true, and you’ll understand why after reading this article.

Unit tests vs regression tests

Regression testing is a way for developers to ensure that their programs continue running as expected after introducing new features or changes. For example, if an update adds functionality that causes bugs in the existing code base, this will be discovered during regression testing before releasing the bug to production.

The goal is to prevent bugs from breaking into new releases, so regression testing helps developers identify what’s working and what isn’t. Unit testing is just one type of regression testing.

Regression testing is an important technique that every developer should know about.

The role of unit testing in DevOps – How does unit testing fit into the DevOps process?

devops

Unit testing is an essential part of DevOps because it ensures that the code is reliable and does not break existing functionality.

Unit testing also allows developers to automate the testing process, which speeds up the development process and ensures that all code is tested before deployment. You can also run automated tests regularly to ensure that the code remains stable over time.

Automated testing has changed the role of the QA team.

Good automated testing can eliminate the most effort of the manual testing process. This leaves the QA team responsible for trying to break the application, identifying issues where automation falls short, and verifying functionality with business stakeholders.

The QA can also spend more time writing automated UI tests. Automated test scripts are more reliable than manual testing because they run faster, use consistent inputs, and cover more of your product’s code than a human would be able to test manually. As long as your tests intelligently perform thorough regression checks, QA will be able to focus on testing at a level that is impossible for automation tests.

How to test legacy code?

Legacy code is poorly documented, un-tested code leftover from the previous developer that no one knows how to update.

If the code doesn’t have tests, it’s difficult to determine whether it works as intended. And you can never know if a change in one part of the legacy code has caused a breaking change somewhere else.

In many cases, many parts of the business logic are stored in legacy code, making it hard to update without breaking other aspects of the site.

So, how to retroactively add tests to a codebase?

You need characterization tests. Characterization tests are particular types of tests that are designed to understand how the legacy code works.

They help answer the question: “How does this unit work, and what are the expected results?”

Once you write those tests and understand how the unit under test behaves, you can start changing the code more safely. Of course, those tests can be unit tests, but more often is easier to write integration tests for the legacy code. Then, once you have those tests in place, you can refactor the code to write unit tests for it.

Unit testing is an important topic. Many books are written on unit testing to help you master this technique. Here are the top 3 recommendations to help you get started.

  1. The Art of Unit Testing: with examples in C# – Roy Osherove
  2. Test-Driven Development: By Example – Kent Beck
  3. Working Effectively with Legacy Code – Michael C. Feathers

You can read the detailed review about these and four other books in a separate article (note: number 6 from that list is super helpful for becoming a master of software development).

Use cases – How to write unit tests for different structures in your code

There are many use cases in your code that you can test. The following list represents some of the structures that you’ll want to consider testing sooner or later.

How to test a private method

You don’t. Here’s why and what you can do instead.

How to test a void method

There are three different ways to test a method that doesn’t return a result.

How to test that the exception was thrown

The following post will show you two different ways how to write unit tests for your C# code that checks for exceptions.

How to test code that contains DateTime.Now

You can use four golden strategies to write a unit test for code that contains DateTime.Now or DateTime.UtcNow.

How to write tests for abstract class

Here are two reasons why most unit tests for abstract classes fail.

Conclusion

Testing is a significant part of every application. It ensures the application behaves as expected, minimizes the number of bugs, and improves your confidence while refactoring.

In this article, you looked at unit testing in C# and how it can help you write better applications. You saw how unit tests work and how to create them using the xUnit testing framework. You also looked at some best practices for unit testing, such as writing good test cases and avoiding dependency on external resources.

Although unit testing may seem like an extra step, it isn’t. Instead, it’s a habit that makes you more confident while coding and helps you find and fix bugs sooner.

That’s why it should be part of every developer’s toolbox.

Recent Posts