Unit testing with xUnit

C# · Practical · 12 min

Tests let you refactor with confidence and are expected in every professional .NET codebase.

.NET has three popular test frameworks: xUnit, NUnit and MSTest. They work the same way: a separate test project references the code under test, and dotnet test discovers and runs the tests.

dotnet new xunit -o Calc.Tests
dotnet add Calc.Tests reference Calc
dotnet test

In xUnit:

  • [Fact] marks a test method with no parameters.
  • [Theory] with [InlineData(...)] runs the same test for several inputs.
  • Assert.Equal(expected, actual), Assert.True, Assert.Null, Assert.Contains, Assert.Throws<T>(() => ...), and await Assert.ThrowsAsync<T>(...) for async code.
  • A new instance of the test class is created for each test, so the constructor is per-test setup and IDisposable.Dispose is teardown. Share expensive setup with class fixtures (IClassFixture<T>).

Follow Arrange, Act, Assert: set up inputs, call the method, check the result. Name tests after behavior (Withdraw_MoreThanBalance_Throws).

To isolate code from databases or HTTP, depend on interfaces and pass fakes; mocking libraries such as NSubstitute or Moq generate them. Test async methods by making the test async Task.

Run a subset with dotnet test --filter "FullyQualifiedName~Calc" and collect coverage with --collect:"XPlat Code Coverage".

Example

// Calc.Tests/CalculatorTests.cs (xUnit project referencing Calc)
using Xunit;
public class CalculatorTests
{
    private readonly Calculator _calc = new();   // fresh per test

    [Fact]
    public void Add_TwoNumbers_ReturnsSum()
        => Assert.Equal(5, _calc.Add(2, 3));     // Act + Assert

    [Theory]
    [InlineData(10, 2, 5)]
    [InlineData(9, 3, 3)]
    public void Divide_ReturnsQuotient(int a, int b, int expected)
        => Assert.Equal(expected, _calc.Divide(a, b));

    [Fact]
    public void Divide_ByZero_Throws()
        => Assert.Throws<DivideByZeroException>(() => _calc.Divide(1, 0));
}
public class Calculator   // normally in the Calc project
{
    public int Add(int a, int b) => a + b;
    public int Divide(int a, int b) => a / b;
}

Remember

  • Tests live in a separate project; dotnet test runs them
  • [Fact] for single cases, [Theory] + [InlineData] for tables of cases
  • xUnit creates a new test class instance per test
  • Depend on interfaces so tests can substitute fakes

Watch out for

  • Tests that depend on each other's state or on execution order.

Check yourself

  1. In xUnit, how do you run one test method with several input sets?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/dotnet/core/testing/