Testing is a critical part of any professional .NET Core application. This chapter covers the most popular testing frameworks, how to write unit tests, mocking dependencies, and adopting a test-driven development workflow.
The .NET SDK includes built-in templates for xUnit, NUnit, and MSTest projects. The most popular choice is xUnit.
dotnet new xunit -n MyApp.Tests
cd MyApp.Tests
dotnet add reference ../MyApp/MyApp.csproj
This creates a test project with xUnit pre-configured. Add a reference to the project you want to test.
xUnit uses the [Fact] attribute for parameterless test methods and [Theory] for data-driven tests.
public class CalculatorTests
{
[Fact]
public void Add_ReturnsSum()
{
var calc = new Calculator();
var result = calc.Add(2, 3);
Assert.Equal(5, result);
}
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, -1, -2)]
[InlineData(0, 0, 0)]
public void Add_WithMultipleInputs_ReturnsExpected(int a, int b, int expected)
{
var calc = new Calculator();
var result = calc.Add(a, b);
Assert.Equal(expected, result);
}
}
The Assert class provides a rich set of static methods: Equal, NotEqual, True, False, Null, NotNull, Throws, InRange, and collection assertions like Contains and All.
Moq is the most widely used mocking library for .NET. It lets you create fake implementations of interfaces.
public interface IUserRepository
{
User GetById(int id);
}
[Fact]
public void GetUser_ReturnsUser_WhenExists()
{
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(r => r.GetById(1))
.Returns(new User { Id = 1, Name = "Alice" });
var service = new UserService(mockRepo.Object);
var user = service.GetUser(1);
Assert.Equal("Alice", user.Name);
mockRepo.Verify(r => r.GetById(1), Times.Once);
}
dotnet add package Moq. Consider NSubstitute or FakeItEasy as lightweight alternatives.NUnit uses [Test] and [TestCase]. It supports setup/teardown via [SetUp] and [TearDown]. MSTest uses [TestMethod] and [DataTestMethod] with [DataRow]. All three frameworks integrate seamlessly with dotnet test.
TDD follows a simple red-green-refactor cycle:
This process produces well-tested, clean, and maintainable code.
Use the CLI or Visual Studio's Test Explorer:
dotnet test # Run all tests
dotnet test --filter "Category=Unit" # Filter by trait
dotnet test --logger "trx;LogFileName=results.trx" # Export results
Test Explorer in VS/VS Code provides a graphical view of passed, failed, and skipped tests with inline diagnostics.
Use dotnet-coverage or Coverlet to measure code coverage:
dotnet add package coverlet.msbuild
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
For ASP.NET Core apps, use Microsoft.AspNetCore.Mvc.Testing with a WebApplicationFactory to spin up an in-memory test server:
public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public ApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Get_Endpoint_ReturnsOk()
{
var response = await _client.GetAsync("/api/values");
response.EnsureSuccessStatusCode();
}
}
StringUtils class that has methods Reverse(string), IsPalindrome(string), and CountWords(string). Write at least 5 [Fact] and 2 [Theory] tests, then run dotnet test to verify all pass.