C# MSTest
last modified October 16, 2026
In this article we show how to do unit testing in C# with the MSTest framework.
Unit testing is a software testing method where individual units (components) of software are tested. The purpose of unit testing is to validate that each unit of the software performs as designed. A unit is the smallest testable part of any software, typically a method or a class.
MSTest is a unit-testing framework from Microsoft. It is available for all .NET languages. Other popular unit-testing frameworks are xUnit and NUnit. The examples were tested with MSTest 4.0 and .NET 10.
The tests are placed in a separate test project, which references the project being tested. Both projects belong to one solution.
Creating the projects
We create a solution with a class library and a test project.
$ mkdir Services $ cd Services $ dotnet new sln $ dotnet new classlib -o Services $ dotnet new mstest -o Services.Tests $ dotnet sln add Services/Services.csproj Services.Tests/Services.Tests.csproj $ dotnet add Services.Tests reference Services/Services.csproj
The dotnet new sln command creates a solution file; since
.NET 10, it uses the XML-based .slnx format. The
mstest template creates a test project. We add both projects to
the solution and reference the library from the test project. We delete the
generated Class1.cs and Test1.cs files.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" Version="4.0.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
</Project>
This is the project file of the test project. The MSTest
metapackage brings the test framework, the test adapter, and the test SDK. The
Using item imports the
Microsoft.VisualStudio.TestTools.UnitTesting namespace in all
files of the project.
C# MSTest simple example
We start with a simple example. We test the following arithmetic functions.
namespace Services;
public static class Basic
{
public static int Add(int a, int b) => a + b;
public static int Sub(int a, int b) => a - b;
public static int Mul(int a, int b) => a * b;
public static int Div(int a, int b) => a / b;
}
The Basic class is part of the Services library.
namespace Services.Tests;
[TestClass]
public sealed class BasicTests
{
[TestMethod]
public void AddTwoNumbers()
{
Assert.AreEqual(5, Basic.Add(2, 3));
}
[TestMethod]
public void SubTwoNumbers()
{
Assert.AreEqual(-1, Basic.Sub(2, 3));
}
}
The class is annotated with the [TestClass] attribute, the test
methods are annotated with the [TestMethod] attribute. MSTest
discovers them automatically.
Assert.AreEqual(5, Basic.Add(2, 3));
We use assertions to ensure the correct output. The first argument of
AreEqual is the expected value, the second is the actual value.
Keeping this order makes the error messages correct.
$ dotnet test ... A total of 1 test files matched the specified pattern. Passed! - Failed: 0, Passed: 2, Skipped: 0, Total: 2, Duration: 20 ms - Services.Tests.dll (net10.0)
The dotnet test command builds the solution and runs all tests.
C# MSTest parameterized tests
A test method can take parameters. The values are provided with the
[DataRow] attribute; the method is executed once for each data
row. In older versions of MSTest, such methods were marked with the
[DataTestMethod] attribute; it is no longer needed.
namespace Services.Tests;
[TestClass]
public sealed class ParamTests
{
[TestMethod]
[DataRow(1, 2, 3)]
[DataRow(2, 2, 4)]
[DataRow(-1, 4, 3)]
public void Add(int x, int y, int expected)
{
Assert.AreEqual(expected, Basic.Add(x, y));
}
[TestMethod]
[DataRow(9, 3, 27)]
[DataRow(-3, -3, 9)]
[Ignore("Not ready yet")]
public void Mul(int x, int y, int expected)
{
Assert.AreEqual(expected, Basic.Mul(x, y));
}
[TestMethod]
[DynamicData(nameof(DivData))]
public void Div(int x, int y, int expected)
{
Assert.AreEqual(expected, Basic.Div(x, y));
}
public static IEnumerable<object[]> DivData =>
[
[9, 3, 3],
[3, 3, 1],
[8, 3, 2]
];
[TestMethod]
public void DivByZero()
{
Assert.ThrowsExactly<DivideByZeroException>(() => Basic.Div(5, 0));
}
}
In this example, we test the arithmetic methods with several sets of values.
[TestMethod] [DataRow(1, 2, 3)] [DataRow(2, 2, 4)] [DataRow(-1, 4, 3)] public void Add(int x, int y, int expected)
The Add method is tested with three sets of values given by the
[DataRow] attributes.
[Ignore("Not ready yet")]
Test methods can be skipped with the [Ignore] attribute. The
optional message explains why the test is skipped.
[DynamicData(nameof(DivData))]
With the [DynamicData] attribute, the test data is provided by a
static property or method. This is useful when the data is computed or too
large for attributes.
Assert.ThrowsExactly<DivideByZeroException>(() => Basic.Div(5, 0));
The Assert.ThrowsExactly method verifies that the code throws an
exception of the given type. The Assert.Throws method also accepts
derived exception types. The older [ExpectedException] attribute
was removed in MSTest 4.
$ dotnet test ... A total of 1 test files matched the specified pattern. Skipped Mul (9,3,27) Skipped Mul (-3,-3,9) Passed! - Failed: 0, Passed: 14, Skipped: 2, Total: 16, Duration: 52 ms - Services.Tests.dll (net10.0)
Each data row counts as a separate test. The two rows of the
Mul test are skipped. The total also includes the palindrome tests
from the next section.
C# MSTest test initialization
The [TestInitialize] attribute marks a method that runs before each
test method of the class. It is used to prepare the objects that the tests
need. The [TestCleanup] attribute marks a method that runs after
each test.
using System.Globalization;
namespace Services;
public class TextTools
{
public bool IsPalindrome(string word)
{
var elements = new List<string>();
var enumerator = StringInfo.GetTextElementEnumerator(word.ToLower());
while (enumerator.MoveNext())
{
elements.Add(enumerator.GetTextElement());
}
return elements.SequenceEqual(Enumerable.Reverse(elements));
}
}
The IsPalindrome method determines if a word is a palindrome. It
works with text elements (grapheme clusters) rather than with
char values, so that it also handles accented letters composed of
several characters correctly.
namespace Services.Tests;
[TestClass]
public sealed class TextToolsTests
{
private TextTools tools = null!;
[TestInitialize]
public void SetUp()
{
tools = new TextTools();
}
[TestMethod]
[DataRow("racecar")]
[DataRow("Level")]
[DataRow("nun")]
public void IsPalindrome(string word)
{
Assert.IsTrue(tools.IsPalindrome(word));
}
[TestMethod]
[DataRow("falcon")]
[DataRow("sky")]
public void IsNotPalindrome(string word)
{
Assert.IsFalse(tools.IsPalindrome(word));
}
}
We test the IsPalindrome method with palindromes and
non-palindromes.
[TestInitialize]
public void SetUp()
{
tools = new TextTools();
}
A new TextTools object is created before each test. Each test thus
starts with a fresh object and the tests do not influence each other. The
null! initializer tells the compiler that the field is assigned
before it is used.
With the normal verbosity of the console logger, we can see the
result of each test:
$ dotnet test --logger "console;verbosity=normal"
...
Passed IsPalindrome ("nun") [6 ms]
Passed IsNotPalindrome ("falcon") [6 ms]
Passed IsPalindrome ("racecar") [6 ms]
Passed IsPalindrome ("Level") [6 ms]
Passed IsNotPalindrome ("sky") [5 ms]
...
The tests run in parallel; the generated MSTestSettings.cs file
enables parallel execution at the method level. Therefore, the order of the
results may vary.
C# MSTest failing test
If we change the last data row of the Div test to
[8, 3, 3], the test fails. Integer division of 8 by 3 gives 2.
$ dotnet test ... Failed Div (8,3,3) [8 ms] Error Message: Assert.AreEqual failed. Expected:<3>. Actual:<2>. 'expected' expression: 'expected', 'actual' expression: 'Basic.Div(x, y)'. Stack Trace: ... Failed! - Failed: 1, Passed: 13, Skipped: 2, Total: 16, Duration: 56 ms - Services.Tests.dll (net10.0)
The error message shows the expected and the actual values and the expressions that produced them.
Source
Unit testing C# with MSTest and .NET
In this article we have done unit testing in C# with the MSTest framework.
Author
List all C# tutorials.