xUnit is the default test framework for .NET. Combined with Moq for mocking, it enables fast, reliable automated testing.

Create a Test Project

  dotnet new xunit -n MyApp.Tests
dotnet add MyApp.Tests reference MyApp
dotnet test
  

Basic Unit Tests

  public class CalculatorTests
{
    [Fact]
    public void Add_ReturnsSum()
    {
        var calc = new Calculator();
        int result = calc.Add(2, 3);
        Assert.Equal(5, result);
    }

    [Theory]
    [InlineData(10, 2, 5)]
    [InlineData(9, 3, 3)]
    [InlineData(0, 5, 0)]
    public void Divide_ReturnsQuotient(int a, int b, int expected)
    {
        var calc = new Calculator();
        Assert.Equal(expected, calc.Divide(a, b));
    }
}

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Divide(int a, int b) => a / b;
}
  

Mocking with Moq

  dotnet add package Moq
  
  public interface IUserRepository
{
    Task<User?> GetByIdAsync(int id);
}

public class UserService
{
    private readonly IUserRepository _repo;
    public UserService(IUserRepository repo) => _repo = repo;

    public async Task<string> GetUserNameAsync(int id)
    {
        var user = await _repo.GetByIdAsync(id);
        return user?.Name ?? "Unknown";
    }
}

public class UserServiceTests
{
    [Fact]
    public async Task GetUserName_ReturnsName_WhenUserExists()
    {
        var mockRepo = new Mock<IUserRepository>();
        mockRepo.Setup(r => r.GetByIdAsync(1))
                .ReturnsAsync(new User { Id = 1, Name = "Alice" });

        var service = new UserService(mockRepo.Object);
        string name = await service.GetUserNameAsync(1);

        Assert.Equal("Alice", name);
        mockRepo.Verify(r => r.GetByIdAsync(1), Times.Once);
    }
}

record User { public int Id { get; init; } public string Name { get; init; } = ""; }
  

Integration Testing ASP.NET Core

  dotnet add package Microsoft.AspNetCore.Mvc.Testing
  
  public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetHello_ReturnsOk()
    {
        var response = await _client.GetAsync("/hello");
        response.EnsureSuccessStatusCode();
        string body = await response.Content.ReadAsStringAsync();
        Assert.Contains("Hello", body);
    }
}
  

Run all tests with dotnet test in CI on every pull request.

Test Organization

  MyApp.Tests/
├── Unit/
│   ├── CalculatorTests.cs
│   └── UserServiceTests.cs
└── Integration/
    └── ApiTests.cs
  

Run subsets: dotnet test --filter "FullyQualifiedName~Unit".

FluentAssertions (Optional)

  dotnet add package FluentAssertions
  
  result.Should().Be(5);
users.Should().HaveCount(3).And.Contain(u => u.Name == "Alice");
  

Readable assertions reduce test maintenance.

Test-Driven Development Workflow

  1. Write a failing test for the desired behavior.
  2. Implement the minimum code to pass.
  3. Refactor while keeping tests green.
  4. Repeat.
  [Fact]
public void IsValidEmail_ReturnsTrue_ForValidEmail()
{
    Assert.True(EmailValidator.IsValid("[email protected]"));
}
  

Code Coverage

  dotnet add package coverlet.collector
dotnet test --collect:"XPlat Code Coverage"
  

Aim for high coverage on business logic; 100% is rarely necessary.

CI Integration

  # GitHub Actions example
- name: Test
  run: dotnet test --no-restore --verbosity normal
  

Run tests on every pull request to catch regressions early.