On this page
Testing with xUnit
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
- Write a failing test for the desired behavior.
- Implement the minimum code to pass.
- Refactor while keeping tests green.
- 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.