blog
Ottorino Bruni  

How to Use xUnit for Unit Testing in .NET Project Using C# in VSCode

Introduction

In software development, it’s crucial to make sure your code works correctly. One of the best ways to do this is through unit testing. Unit testing means testing small parts of your software, like a single function or method, to make sure they work as expected. In this article, we will learn how to use xUnit for unit testing in .NET projects. We will use C# and Visual Studio Code (VSCode) as our tools.

What Are Unit Tests?

Unit tests are small, automated tests that developers write and run. These tests check if a specific part of the software behaves correctly. Unit tests are “isolated,” which means they test just one piece of code without depending on other parts of the application, like databases or external services. This makes it easier to find and fix problems in the code.

Why Is Unit Testing Important?

  1. Finding Bugs Early: Unit tests help you find problems early in the development process, before the software is fully integrated. This makes it easier and faster to fix issues.
  2. Better Code Quality: Writing unit tests encourages you to write clean and well-organized code. When you know your code will be tested, you’re more likely to write good, understandable code.
  3. Safe Refactoring: As you improve or change your code, unit tests help ensure that these changes don’t break existing functionality. This makes it safer to update and improve your software.
  4. Documentation: Unit tests can also help explain how the code should work. They serve as examples of how to use the code correctly.
  5. Team Collaboration: In a team, unit tests help make sure that everyone’s code works together smoothly. This is especially important in large projects where many people are working on different parts of the software.
  6. Cost Efficiency: Finding and fixing bugs early is cheaper than fixing them later in the development process or after the software is released. Unit tests help save time and money by catching issues early.

What is xUnit?

xUnit.net is a free, open-source unit testing framework designed for the .NET Framework. Developed by the original creator of NUnit v2, it is a modern tool for unit testing C# and F# applications, with support for other .NET languages, though they are not officially supported. xUnit.net integrates seamlessly with popular development tools like Visual Studio, Visual Studio Code, ReSharper, CodeRush, and TestDriven.NET. It is a project under the .NET Foundation and adheres to its code of conduct.

xUnit.net

In this guide, we’ll show you how to use xUnit to create unit tests in .NET projects. We will cover the basics and provide simple examples, so you can start using unit testing in your projects. Whether you’re new to unit testing or want to learn more, this article will help you understand the basics and start writing reliable tests.

The Test Pyramid

To effectively approach automated testing for your software, it’s crucial to understand the concept of the Test Pyramid, introduced by Mike Cohn in his book Succeeding with Agile. This model helps you balance the different types of tests and their importance.

The Test Pyramid

The Three Layers of the Test Pyramid

  1. Unit Tests (Bottom Layer)
    • Purpose: Test individual components or functions in isolation.
    • Focus: Ensure each small part of the application works correctly.
    • Characteristics: Fast to run, numerous, and should be the base of your test suite.
  2. Service Tests (Middle Layer)
    • Purpose: Test the interactions between different components or services.
    • Focus: Verify that integrated parts of the application work together as expected.
    • Characteristics: More complex and slower than unit tests, but essential for validating interactions.
  3. User Interface (UI) Tests (Top Layer)
    • Purpose: Test the application’s user interface and overall user experience.
    • Focus: Ensure the application works from an end-user perspective.
    • Characteristics: Slowest to run, fewer in number, but critical for validating the user experience.

By following the Test Pyramid, you ensure a balanced and effective testing strategy, focusing on more unit tests for quick feedback, service tests for integration, and fewer, more thorough UI tests for user experience.

If you want to delve deeper into testing strategies, I recommend reading this insightful article on the Test Pyramid: Martin Fowler’s Practical Test Pyramid.

Test Naming conventions

Naming conventions for tests are crucial for maintaining clarity and consistency in your test suite. A well-chosen convention helps ensure that tests are easy to understand and maintain, which is especially important as projects grow and evolve. Here’s a guide on how to approach naming your tests and a recommendation based on the conventions you’ve mentioned.

Importance of Test Naming

  1. Clarity: Good test names clearly describe what the test does and what behavior is expected. This makes it easier for others (and yourself) to understand the purpose of each test at a glance.
  2. Maintenance: Consistent naming conventions help maintain the test suite. If the method name changes, having a clear and consistent naming convention helps in updating the test names accordingly.
  3. Documentation: Test names can serve as documentation. They explain what conditions are being tested and what the expected outcomes are, which is helpful for both current and future developers.

There are various ways to name test methods, and the best approach can depend on the project’s requirements and context. For clarity and consistency, I will use the MethodName_StateUnderTest_ExpectedBehavior convention in this blog.

Avoiding Code Duplication in Unit Tests with xUnit

When writing unit tests, it’s common to encounter situations where only the input parameters differ between multiple tests. Copying and pasting test code to handle different inputs can lead to code duplication and increased maintenance overhead. Fortunately, xUnit provides attributes that help manage this scenario more efficiently.

Understanding xUnit Attributes

1. [Theory] Attribute

The [Theory] attribute is used in xUnit to denote a test method that runs with a variety of inputs. Instead of writing multiple test methods for each different input, you can write one test method and provide different inputs to it. This approach helps keep your test code concise and focused.

2. [InlineData] Attribute

The [InlineData] attribute is used in conjunction with [Theory] to specify the different sets of input data for the test method. Each [InlineData] attribute represents a set of arguments that are passed to the test method. The test method will be executed once for each set of inputs.

Example: How to Use xUnit for Unit Testing

Disclaimer: This example is purely for educational purposes. There are better ways to write code and applications that can optimize this example. Use this as a starting point for learning, but always strive to follow best practices and improve your implementation.

GitHub Repository

All the code is available in this GitHub repository. This repository will help you easily apply the new modifications to the previously created code thanks to the use of tags and branches.

In this example, we’ll set up a .NET solution with two projects: a console application called UnitTestConsoleApp and a test project called UnitTestConsoleApp.Tests. We’ll use xUnit for unit testing.

Prerequisites

Before starting, make sure you have the following installed:

  • .NET SDK: Download and install the .NET SDK if you haven’t already.
  • Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.
  • C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.

Step 1: Create a Solution and Console Application

  1. Open a Terminal or Command Prompt: You can use the terminal in VSCode or any command prompt.
  2. Create a New Solution:
    dotnet new sln -n UnitTestSolution
    

    This command creates a new solution file named UnitTestSolution.sln.

  3. Create a New Console Application:
    dotnet new console -n UnitTestConsoleApp
    

    This command creates a new directory called UnitTestConsoleApp with a basic .NET console application.

  4. Navigate to the Console Application Directory:
    cd UnitTestConsoleApp
    
  5. Create the UserAccount Class: In the UnitTestConsoleApp project directory, create a new file named UserAccount.cs and add the following code:
    public class UserAccount
    {
      private string _email;
    
      public void SetEmail(string email)
      {
        if (string.IsNullOrWhiteSpace(email))
        {
          throw new ArgumentException("Email cannot be null or empty");
        }
    
        _email = email;
      }
    
      public string GetEmail()
      {
        return _email;
      }
    }
  6. Add the Console Application to the Solution:
    dotnet sln add UnitTestConsoleApp/UnitTestConsoleApp.csproj
    

    This command adds the console application project to the solution.

    UserAccount Class

Step 2: Create and Configure the Test Project

  1. Create a New xUnit Test Project:
    dotnet new xunit -n UnitTestConsoleApp.Tests
    

    This command creates a new directory called UnitTestConsoleApp.Tests with a basic xUnit test project.

  2. Navigate to the Test Project Directory:
    cd UnitTestConsoleApp.Tests
    
  3. Add xUnit Package: Add the necessary NuGet packages for xUnit:
    dotnet add package xunit
  4. Reference the Console Application: Add a project reference from the test project to the console application:
    dotnet add reference ../UnitTestConsoleApp/UnitTestConsoleApp.csproj
    
  5. Add the Test Project to the Solution: Navigate back to the solution root and add the test project:
    dotnet sln add UnitTestConsoleApp.Tests/UnitTestConsoleApp.Tests.csproj
    
Unit Tests with xUnit

Step 3: Write Unit Tests Using xUnit

  1. Open the Test Project in VSCode: Ensure you have the UnitTestConsoleApp.Tests project open in VSCode.
  2. Create Your Test File: For example, create a UserAccountTests.cs file in the UnitTestConsoleApp.Tests directory.
  3. Write Your Tests: Here’s a sample test file using xUnit:
    using Xunit;
    using System;
    using UnitTestConsoleApp;
    
    namespace UnitTestConsoleApp.Tests
    {
      public class UserAccountTests
      {
        private UserAccount _userAccount;
    
        // This method is run before each test
        public UserAccountTests()
        {
          _userAccount = new UserAccount();
        }
    
        [Fact]
        public void SetEmail_ValidEmail_EmailIsSet()
        {
          // Arrange
          string email = "test@example.com";
    
          // Act
          _userAccount.SetEmail(email);
    
          // Assert
          Assert.Equal(email, _userAccount.GetEmail());
        }
    
        [Fact]
        public void SetEmail_EmptyEmail_ThrowsArgumentException()
        {
          // Arrange
          string emptyEmail = "";
    
          // Act and Assert
          var exception = Assert.Throws<ArgumentException>(() => _userAccount.SetEmail(emptyEmail));
          Assert.Equal("Email cannot be null or empty", exception.Message);
        }
    
        [Fact]
        public void GetEmail_NoEmailSet_ReturnsNull()
        {
          // Act
          string email = _userAccount.GetEmail();
    
          // Assert
          Assert.Null(email);
        }
    
        [Theory]
        [InlineData("user@example.com")]
        [InlineData("admin@domain.com")]
        [InlineData("support@company.org")]
        public void SetEmails_ValidEmail_EmailIsSet(string email)
        {
          // Act
          _userAccount.SetEmail(email);
    
          // Assert
          Assert.Equal(email, _userAccount.GetEmail());
        }
    
        [Theory]
        [InlineData("")]
        [InlineData(null)]
        [InlineData(" ")] // Testing a single space as input
        public void SetEmail_InvalidEmail_ThrowsArgumentException(string invalidEmail)
        {
          // Act and Assert
          var exception = Assert.Throws<ArgumentException>(() => _userAccount.SetEmail(invalidEmail));
          Assert.Equal("Email cannot be null or empty", exception.Message);
        }
      }
    }

Step 4: Run Your Tests

  1. Run Tests Using the Command Line: From the root directory of your solution, execute:
    dotnet test
    

    This command runs all the tests in your solution and displays the results in the terminal.

  2. Run Tests in VSCode: You can also run tests directly within VSCode by using the Test Explorer extension or by running the tests from the terminal.
run dotnet test

Conclusions

Writing unit tests is an essential practice in software development, but it’s important to remember that having a large number of unit tests does not guarantee bug-free code. Unit tests are a valuable tool for identifying issues early and ensuring that individual components of your code work as expected. However, they are just one part of a comprehensive testing strategy.

Benefits of Unit Testing

  1. Early Detection of Bugs: Unit tests help catch errors in individual components before they propagate through the application.
  2. Improved Code Quality: Writing tests encourages better design and more modular code, making it easier to understand and maintain.
  3. Refactoring Confidence: With a robust suite of unit tests, you can refactor code with confidence, knowing that you have tests in place to catch unintended changes.
  4. Documentation: Unit tests serve as a form of documentation, demonstrating how different parts of the application are expected to behave.

In the next article, we will explore Moq, a powerful library for creating mock objects in unit tests. Moq can help simulate various scenarios and interactions between components, allowing you to write more comprehensive and flexible tests. Stay tuned to learn how to leverage Moq to enhance your unit testing strategy.

 

If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.

Thanks for reading!

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.