xUnit Testing: A Practical Tutorial
Create an xUnit.net v3 project, write your first facts and theories, and run tests with the right runner. Includes version guidance, troubleshooting, and a complete example.
xUnit.net tests C#, F#, and Visual Basic code. For a new C# project, this tutorial uses the official xUnit.net v3 template: create a test project, mark an invariant check with [Fact], add input-driven cases with [Theory] and [InlineData], then run the generated executable with dotnet run. The examples target xUnit.net v3 and require .NET 8 or later, or .NET Framework 4.7.2 or later; the .NET Framework target is officially supported only on Windows. If you maintain an xUnit.net v2 project, use its matching package and runner instructions instead of mixing v2 and v3 setup.
1. Check the version and prerequisites
The commands below follow the current v3 getting-started path. xUnit.net’s guide records its example snapshot as xUnit.net v3 4.0.0-pre.108 and .NET SDK 10.0.102. Those are documentation snapshot versions, not recommendations to pin blindly. Before adding explicit package versions or choosing a target framework, check the current xUnit.net v3 getting-started guide and its compatibility information.
- Install a compatible .NET SDK and confirm it is available with
dotnet --info. - Choose a supported target: .NET 8 or later, or .NET Framework 4.7.2 or later. .NET Framework support is Windows-only.
- Decide which runner you intend to use. The documented v3 template uses Microsoft Testing Platform (MTP) by default. Visual Studio Test Explorer, Visual Studio Code’s Testing panel, and workflows based on VSTest need the VSTest adapter setup described below.
2. Create and run an xUnit.net v3 project
Install the official templates, create a C# test project, and run it from its directory:
dotnet new install xunit.v3.templates
dotnet new xunit3 -n Calculator.Tests
cd Calculator.Tests
dotnet run
The templates also support F# and VB.NET. The generated v3 project is configured for MTP using xunit.v3.mtp-v2. Its stand-alone executable can run tests directly. Keep this project configuration and its documented runner path together; dotnet run is the command shown for this generated project.
If you prefer a solution with a production project and a separate test project, Microsoft Learn’s C# xUnit tutorial shows that workflow and uses dotnet test. Do not assume every v3 runner setup uses the same command or dependencies.
3. Write a first fact with a meaningful assertion
A fact checks a behavior that should hold for the case regardless of input data. Start with a small deterministic method so the test’s expected result is clear. Here is a complete minimal test project file and test class using the generated v3 project’s package setup:
// Calculator.Tests.csproj is created by the xunit3 template.
// Keep its generated package and runner configuration.
// CalculatorTests.cs
using Xunit;
public class CalculatorTests
{
[Fact]
public void Add_TwoPositiveNumbers_ReturnsTheirSum()
{
var result = Calculator.Add(2, 3);
Assert.Equal(5, result);
}
}
public static class Calculator
{
public static int Add(int left, int right) => left + right;
}
In a real solution, put Calculator in the production project and reference that project from the test project. This compact example keeps the behavior and its test together so the first run is self-contained. The assertion checks the output that matters; Assert.True(true) would pass without checking any behavior.
A test-first workflow is useful when implementing a new behavior: write a test that describes the result, run it to see the failure, implement the behavior, then rerun the test. Microsoft Learn illustrates this cycle with a prime-checking service. A failing test at the start is expected when the behavior has not been implemented; a failure after implementation indicates that either the behavior or expectation needs attention.
4. Add a theory for related input cases
xUnit.net describes the distinction this way: “Facts are tests which are always true. They test invariant conditions.” The documentation describes theories as tests that are true for a particular set of data. Use [Theory] when the same logic should be checked against several inputs. Each [InlineData] row is reported as its own test case.
using Xunit;
public class CalculatorTests
{
[Theory]
[InlineData(2, 3, 5)]
[InlineData(0, 0, 0)]
[InlineData(-4, 6, 2)]
public void Add_ReturnsExpectedSum(int left, int right, int expected)
{
Assert.Equal(expected, Calculator.Add(left, right));
}
}
public static class Calculator
{
public static int Add(int left, int right) => left + right;
}
Keep rows focused on meaningful behavior: ordinary values, boundary values, and cases that previously caused a defect. When a row fails, the runner’s individual case output helps identify the arguments involved. For data that is more complex than a few inline values, use an appropriate xUnit.net data source; consult the v3 documentation for the supported forms rather than forcing large objects into attributes.
5. Choose the runner and run tests
| Setup | Use it when | Execution |
|---|---|---|
| Generated v3 template with MTP | You want the v3 guide’s default stand-alone executable path. | From the test project directory, run dotnet run. |
| VSTest | You need VSTest-based IDE integration or a workflow built around dotnet test. |
Add xunit.runner.visualstudio and Microsoft.NET.Test.Sdk as documented, then use the matching VSTest workflow. |
Runner dependencies are part of the project setup, not interchangeable decorations. For VSTest, follow the official v3 runner instructions for package configuration. Microsoft Learn’s tutorial demonstrates dotnet test; the v3 guide demonstrates dotnet run for its generated MTP project. Use Visual Studio Test Explorer or Visual Studio Code’s Testing panel with the VSTest adapter configuration they require.
6. Read failures and improve the test
A useful failure report identifies the test and, for a theory, the data case that failed. An equality assertion reports the expected and actual values. Use that information to decide whether the implementation is wrong, the expectation is wrong, or the test is exercising an unintended case.
- Find the failed test name and theory arguments in the output.
- Compare expected and actual values and inspect the behavior under test.
- Fix the implementation or correct the test expectation based on the intended behavior.
- Run the same project again, then run the broader solution test command used by your chosen runner.
Prefer descriptive names that state the condition and outcome, such as Add_TwoPositiveNumbers_ReturnsTheirSum. Tests should be deterministic: avoid dependence on current time, network state, or shared mutable data unless that dependency itself is what the test is designed to control.
7. xUnit.net v2 and v3 are different setup paths
Do not silently apply v3 project and runner instructions to an existing v2 solution. The official migration guide describes v3 projects as stand-alone executables and v2 projects as library projects that depend on a runner; v3 also raises the minimum supported targets to .NET 8 and .NET Framework 4.7.2. Existing v2 projects should follow the v2 getting-started guide or assess changes using the v3 migration guide. Check target frameworks, package references, and CI execution before migrating.
8. Troubleshooting common setup issues
| Symptom | Likely cause | What to do |
|---|---|---|
dotnet new xunit3 is not recognized |
The xUnit v3 template package is not installed, or the template list is stale. | Run dotnet new install xunit.v3.templates, then check available templates with dotnet new list xunit. |
dotnet run does not find tests |
The command is running outside the generated test project, or the project does not use the template’s MTP setup. | Change to the test project directory and inspect its project file and runner packages. Follow the command for the configured runner. |
dotnet test discovers no tests |
The project may use the MTP template path while the command or IDE is using VSTest, or VSTest adapter dependencies are missing. | Use dotnet run for the documented generated MTP project. For VSTest, configure xunit.runner.visualstudio and Microsoft.NET.Test.Sdk per the v3 guide. |
| IDE test panel is empty | The IDE integration and test runner configuration do not match. | For VSTest-based Test Explorer or VS Code Testing panel usage, add the documented VSTest dependencies and reload the project. Otherwise run the MTP executable path. |
| Target framework or package compatibility error | A v3 setup is targeting an unsupported framework, or package instructions from different major versions were combined. | Use .NET 8 or later, or .NET Framework 4.7.2 or later on Windows, for v3. For v2, follow the v2 guide. Recheck current compatibility documentation. |
| A theory has one failing case | One input row exposes a behavior or expected value that differs from the others. | Read the failing row’s arguments and expected/actual values; fix the implementation or the case expectation. |
9. Performance, reliability, and cost
Unit tests are most useful when they are fast, repeatable, and isolated. Keep simple logic tests free of external services, avoid shared mutable state between cases, and use the smallest test set that clearly covers the behavior. The sources here do not establish performance benchmarks or a universal test runtime; measure your own suite if execution time becomes a concern. Framework and runner compatibility can change, so check the official documentation when upgrading SDKs, packages, or CI images.
The xUnit.net framework and .NET SDK setup described here do not imply a per-test service charge. CI infrastructure, build minutes, and any external services your tests call may have separate costs under their providers’ terms. Tests that make network requests can also fail for reasons unrelated to the code under test, so isolate those dependencies when the goal is to test unit behavior.
10. Capture pages while documenting test results
If your engineering documentation needs website captures alongside test output, ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. A screenshot is separate from running xUnit tests, but it can help capture a rendered page used in documentation or a visual review. Here is the one-request version; see the ScreenshotNeo API documentation for options and configuration.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
It removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, and cache hits are not billed, and the response identifies page verdict and billing status in headers. An MCP server exposes take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month without a card; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Frequently asked questions
Can I use xUnit.net with languages other than C#?
Yes. xUnit.net supports C#, F#, and Visual Basic. The documented v3 templates are available for all three.
Should every test be a theory?
No. Use a fact for a single invariant check and a theory when the same test logic should run for multiple data sets.
Can I keep a v2 test project and add v3 packages?
Do not combine major-version setup instructions casually. Check the official migration guidance and plan the project, framework, runner, and CI changes as a migration.


