← Back to Tutorials Chapter 9

Testing

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.

Creating a Test Project

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.

Writing Tests with xUnit

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);
    }
}

Assert Class

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.

Live-Dependency Testing with Moq

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);
}
Moq is available via dotnet add package Moq. Consider NSubstitute or FakeItEasy as lightweight alternatives.

NUnit and MSTest 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.

Test-Driven Development (TDD) Workflow

TDD follows a simple red-green-refactor cycle:

  1. Red: Write a failing test for the desired behavior.
  2. Green: Write the minimum code to make the test pass.
  3. Refactor: Improve the code while keeping tests green.

This process produces well-tested, clean, and maintainable code.

.NET Core testing

Running Tests

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.

Code Coverage

Use dotnet-coverage or Coverlet to measure code coverage:

dotnet add package coverlet.msbuild
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
Aim for at least 70-80% coverage on business logic. Focus on testing behavior, not implementation details.

Integration Testing

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();
    }
}
Exercise: Create an xUnit test project for a simple 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.