Loading...

Writing Unit Tests in C# (xUnit, NUnit, MSTest)

Learn how to write unit tests in C# using xUnit, NUnit, and MSTest to build reliable and maintainable applications.

In software development, unit testing ensures that the smallest parts of an application (method, class, or function) work correctly in isolation. In C#, the most commonly used testing frameworks are xUnit, NUnit, and MSTest. Unit tests help detect errors early, improve code quality, and make safe refactoring possible.


What is a Unit Test?

Unit tests verify whether a method produces the correct output for given inputs. Tests are usually isolated per scenario and decoupled from external dependencies (database, file system, network access).


// Example: Method to be tested
public class Calculator
{
    public int Add(int a, int b) => a + b;
}

In the example above, the Add method is the unit to be tested.


Creating a Test Project

You can easily create a test project using Visual Studio or the .NET CLI:


// Create a test project using xUnit
dotnet new xunit -n Calculator.Tests
cd Calculator.Tests

// Add a reference to the main project
dotnet add reference ../Calculator/Calculator.csproj

Alternatively, you can use nunit or mstest templates:


dotnet new nunit -n ProjectName.Tests
dotnet new mstest -n ProjectName.Tests

Writing Tests with xUnit

xUnit is a modern and flexible testing framework widely used in the C# ecosystem. The [Fact] attribute marks individual tests, while [Theory] represents parameterized tests.


using Xunit;

public class CalculatorTests
{
    [Fact]
    public void Add_ShouldReturnCorrectResult()
    {
        // Arrange
        var calc = new Calculator();

        // Act
        int result = calc.Add(2, 3);

        // Assert
        Assert.Equal(5, result);
    }

    [Theory]
    [InlineData(1, 2, 3)]
    [InlineData(-1, 5, 4)]
    [InlineData(10, -2, 8)]
    public void Add_ShouldWorkWithDifferentValues(int a, int b, int expected)
    {
        var calc = new Calculator();
        Assert.Equal(expected, calc.Add(a, b));
    }
}

Fact: Tests a single scenario. Theory: Tests the same method with multiple data sets.


Writing Tests with NUnit

NUnit is a powerful and long-standing testing framework in the C# world. It’s known for its simplicity and extensive set of assertions.


using NUnit.Framework;

[TestFixture]
public class CalculatorTests
{
    private Calculator calc;

    [SetUp]
    public void Setup()
    {
        calc = new Calculator();
    }

    [Test]
    public void Add_Test()
    {
        int result = calc.Add(4, 6);
        Assert.That(result, Is.EqualTo(10));
    }

    [TestCase(1, 2, 3)]
    [TestCase(5, 5, 10)]
    public void Add_WithDifferentValues_ShouldReturnCorrectResult(int a, int b, int expected)
    {
        Assert.That(calc.Add(a, b), Is.EqualTo(expected));
    }
}

In NUnit, the [SetUp] method runs before each test, and [TestCase] is used for parameterized tests.


Writing Tests with MSTest

MSTest is Microsoft’s built-in testing framework that comes with Visual Studio. It’s often used in enterprise-level projects.


using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class CalculatorTests
{
    private Calculator calc;

    [TestInitialize]
    public void Init()
    {
        calc = new Calculator();
    }

    [TestMethod]
    public void Add_Test()
    {
        int result = calc.Add(2, 8);
        Assert.AreEqual(10, result);
    }

    [DataTestMethod]
    [DataRow(1, 1, 2)]
    [DataRow(-3, 5, 2)]
    public void Add_WithDifferentValues_ShouldWorkProperly(int a, int b, int expected)
    {
        Assert.AreEqual(expected, calc.Add(a, b));
    }
}

In MSTest, the [TestInitialize] attribute handles setup before each test, and [DataTestMethod] is used for data-driven testing.


Assert Methods

All testing frameworks include similar assertion structures:


// Example: Exception test
[Fact]
public void Divide_ShouldThrowDivideByZeroException()
{
    var calc = new Calculator();
    Assert.Throws<DivideByZeroException>(() => calc.Divide(5, 0));
}

Running Tests

You can run tests via the CLI or Visual Studio:


// Run tests using .NET CLI
dotnet test

In Visual Studio, you can use the “Test Explorer” window to run tests and view passed/failed results in a graphical interface.


Mocking and Dependency Isolation

Mocking is used to isolate real dependencies from the test environment. This ensures that tests only verify the target method and are not affected by external factors (e.g., databases, services).


using Moq;
using Xunit;

public interface IEmailService
{
    void Send(string address, string message);
}

public class UserRegistration
{
    private readonly IEmailService _mail;
    public UserRegistration(IEmailService mail) => _mail = mail;

    public void Register(string name)
    {
        _mail.Send("admin@site.com", $"{name} has been registered.");
    }
}

public class UserRegistrationTests
{
    [Fact]
    public void Register_ShouldSendEmail()
    {
        var mock = new Mock<IEmailService>();
        var reg = new UserRegistration(mock.Object);

        reg.Register("John");

        mock.Verify(m => m.Send("admin@site.com", "John has been registered."), Times.Once);
    }
}

The Moq library creates mock objects to isolate the test environment.


Test Organization and Naming


Real-World Example: Order Total Calculation Test

The following example tests a simple order total calculation.


// Application code
public class Order
{
    public List<decimal> ProductPrices { get; set; } = new();
    public decimal Total() => ProductPrices.Sum();
}

// Test
public class OrderTests
{
    [Fact]
    public void Total_ShouldReturnCorrectSum()
    {
        var order = new Order();
        order.ProductPrices.AddRange(new[] { 10m, 20m, 30m });
        Assert.Equal(60m, order.Total());
    }
}

This example provides a basic unit test to ensure that business logic works correctly.


Performance and Best Practices


TL;DR

  • Unit Test: Tests small, isolated pieces of code.
  • xUnit: Modern, minimal, and widely used in the .NET community.
  • NUnit: Classic framework with strong assertion and attribute-based structure.
  • MSTest: Microsoft’s built-in solution, fully integrated with Visual Studio.
  • Mocking: Isolates external dependencies using fake objects.
  • AAA Model: Follow the Arrange–Act–Assert test pattern.
  • The dotnet test command runs all tests from the CLI.

Related Articles