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
xunitxunit.runner.visualstudioMicrosoft.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
NUnitNUnit3TestAdapterMicrosoft.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.TestFrameworkMSTest.TestAdapterMicrosoft.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 testVisual 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.