How to Write Unit Tests in C# With xUnit, NUnit, and MSTest

This article provides a direct, practical guide on how to write unit tests in C# using the three primary .NET test frameworks: xUnit, NUnit, and MSTest. You will learn the fundamental structure of a unit test, the standard Arrange-Act-Assert pattern, and how to write basic and parameterized test methods using the distinct attributes and assertion syntaxes of each framework.


The Sample Code Under Test

To demonstrate each framework consistently, consider this simple C# class:

namespace MyProject;

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public double Divide(double a, double b)
    {
        if (b == 0) throw new DivideByZeroException("Cannot divide by zero.");
        return a / b;
    }
}

Every unit test typically follows the AAA (Arrange, Act, Assert) pattern:

  • Arrange: Set up test data, objects, and dependencies.
  • Act: Execute the method or behavior being tested.
  • Assert: Verify that the result matches expectations.

1. Writing Unit Tests with xUnit

xUnit is modern, opinionated, and the most widely used testing framework in the .NET ecosystem. It creates a new instance of the test class for every single test method to ensure complete test isolation.

Required Packages

  • xunit
  • xunit.runner.visualstudio
  • Microsoft.NET.Test.Sdk

Key Attributes & Syntax

  • [Fact]: Marks a normal test that takes no parameters.
  • [Theory]: Marks a parameterized test executed multiple times with different inputs.
  • [InlineData]: Passes parameters into a [Theory].
  • Class attributes: None required; xUnit recognizes any public class containing test methods.

Code Example

using Xunit;
using MyProject;

namespace MyProject.Tests.XUnit;

public class CalculatorTests
{
    private readonly Calculator _calculator = new();

    [Fact]
    public void Add_TwoPositiveNumbers_ReturnsSum()
    {
        // Arrange
        int a = 5, b = 3;

        // Act
        int result = _calculator.Add(a, b);

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

    [Theory]
    [InlineData(2, 2, 4)]
    [InlineData(-1, 1, 0)]
    [InlineData(0, 0, 0)]
    public void Add_MultipleInputs_ReturnsCorrectSum(int a, int b, int expected)
    {
        int result = _calculator.Add(a, b);
        Assert.Equal(expected, result);
    }

    [Fact]
    public void Divide_ByZero_ThrowsDivideByZeroException()
    {
        Assert.Throws<DivideByZeroException>(() => _calculator.Divide(10, 0));
    }
}

2. Writing Unit Tests with NUnit

NUnit is a mature, feature-rich testing framework ported originally from JUnit. By default, it reuses the same test class instance across tests unless configured otherwise.

Required Packages

  • NUnit
  • NUnit3TestAdapter
  • Microsoft.NET.Test.Sdk

Key Attributes & Syntax

  • [TestFixture]: Identifies a class containing tests (optional in newer versions, but common practice).
  • [Test]: Marks a parameterless test method.
  • [TestCase]: Passes inline data into a parameterized test.
  • Assert.That(): NUnit's constraint-based assertion model.

Code Example

using NUnit.Framework;
using MyProject;

namespace MyProject.Tests.NUnitTests;

[TestFixture]
public class CalculatorTests
{
    private Calculator _calculator;

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

    [Test]
    public void Add_TwoPositiveNumbers_ReturnsSum()
    {
        // Arrange & Act
        int result = _calculator.Add(5, 3);

        // Assert
        Assert.That(result, Is.EqualTo(8));
    }

    [TestCase(2, 2, 4)]
    [TestCase(-1, 1, 0)]
    [TestCase(0, 0, 0)]
    public void Add_MultipleInputs_ReturnsCorrectSum(int a, int b, int expected)
    {
        int result = _calculator.Add(a, b);
        Assert.That(result, Is.EqualTo(expected));
    }

    [Test]
    public void Divide_ByZero_ThrowsDivideByZeroException()
    {
        Assert.Throws<DivideByZeroException>(() => _calculator.Divide(10, 0));
    }
}

3. Writing Unit Tests with MSTest

MSTest is Microsoft's built-in testing framework for Visual Studio and .NET. It uses explicit class and method decorators.

Required Packages

  • MSTest.TestFramework
  • MSTest.TestAdapter
  • Microsoft.NET.Test.Sdk

Key Attributes & Syntax

  • [TestClass]: Required attribute on the class containing tests.
  • [TestMethod]: Marks an individual test method.
  • [DataTestMethod] and [DataRow]: Used for parameterized testing.
  • Assert: Classical assertion methods (Assert.AreEqual, Assert.ThrowsException).

Code Example

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyProject;

namespace MyProject.Tests.MSTestTests;

[TestClass]
public class CalculatorTests
{
    private Calculator _calculator;

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

    [TestMethod]
    public void Add_TwoPositiveNumbers_ReturnsSum()
    {
        // Arrange & Act
        int result = _calculator.Add(5, 3);

        // Assert
        Assert.AreEqual(8, result);
    }

    [DataTestMethod]
    [DataRow(2, 2, 4)]
    [DataRow(-1, 1, 0)]
    [DataRow(0, 0, 0)]
    public void Add_MultipleInputs_ReturnsCorrectSum(int a, int b, int expected)
    {
        int result = _calculator.Add(a, b);
        Assert.AreEqual(expected, result);
    }

    [TestMethod]
    public void Divide_ByZero_ThrowsDivideByZeroException()
    {
        Assert.ThrowsException<DivideByZeroException>(() => _calculator.Divide(10, 0));
    }
}

Framework Comparison Summary

Feature xUnit NUnit MSTest
Test Class Indicator None required [TestFixture] [TestClass]
Basic Test Method [Fact] [Test] [TestMethod]
Data-Driven Method [Theory] [TestCase] [DataTestMethod]
Data Row Values [InlineData(...)] [TestCase(...)] [DataRow(...)]
Setup Attribute Constructor [SetUp] [TestInitialize]
Teardown Attribute IDisposable.Dispose() [TearDown] [TestCleanup]
Assertion Style Assert.Equal(exp, act) Assert.That(act, Is.EqualTo(exp)) Assert.AreEqual(exp, act)

Running the Tests

Regardless of which framework you use, you can execute all tests in your solution using the .NET CLI from your project's root folder:

dotnet test

Visual Studio and Visual Studio Code (via the C# Dev Kit extension) will also automatically discover, display, and run tests written in any of these three frameworks inside the Test Explorer.