xUnit Setup
dotnet new xunit -o MyApp.Tests
dotnet add MyApp.Tests reference MyApp.Application
dotnet add package Moq
dotnet add package FluentAssertions
Basic Test (AAA Pattern)
public class CalculatorTests {
[Fact]
public void Add_TwoNumbers_ReturnsSum() {
// Arrange
var calc = new Calculator();
// Act
var result = calc.Add(2, 3);
// Assert
Assert.Equal(5, result);
}
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void Add_VariousInputs_ReturnsExpected(int a, int b, int expected) {
var calc = new Calculator();
var result = calc.Add(a, b);
Assert.Equal(expected, result);
}
}
Moq — Mocking Dependencies ⭐
public class ProductServiceTests {
[Fact]
public async Task GetById_WhenExists_ReturnsProduct() {
// Arrange
var mockRepo = new Mock<IProductRepository>();
var product = new Product { Id = 1, Name = "Laptop", Price = 50000 };
mockRepo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(product);
var service = new ProductService(mockRepo.Object);
// Act
var result = await service.GetByIdAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal("Laptop", result.Name);
mockRepo.Verify(r => r.GetByIdAsync(1), Times.Once);
}
[Fact]
public async Task Create_ValidProduct_CallsRepo() {
var mockRepo = new Mock<IProductRepository>();
var service = new ProductService(mockRepo.Object);
await service.CreateAsync(new ProductDto("Phone", 30000));
mockRepo.Verify(r => r.AddAsync(It.IsAny<Product>()), Times.Once);
mockRepo.Verify(r => r.SaveChangesAsync(), Times.Once);
}
}
FluentAssertions — Readable Asserts
using FluentAssertions;
result.Should().NotBeNull();
result.Name.Should().Be("Laptop");
result.Price.Should().BeGreaterThan(0);
list.Should().HaveCount(3);
list.Should().Contain(x => x.Id == 1);
action.Should().Throw<ArgumentException>().WithMessage("*invalid*");
EF Core — In-Memory Database
dotnet add package Microsoft.EntityFrameworkCore.InMemory
[Fact]
public async Task GetAll_ReturnsAllProducts() {
var opt = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase("TestDb_" + Guid.NewGuid())
.Options;
using var ctx = new AppDbContext(opt);
ctx.Products.AddRange(
new Product { Name = "P1", Price = 100 },
new Product { Name = "P2", Price = 200 });
await ctx.SaveChangesAsync();
var repo = new ProductRepository(ctx);
var all = await repo.GetAllAsync();
all.Should().HaveCount(2);
}
Test Lifecycle
public class MyTests : IClassFixture<DbFixture>, IDisposable {
private readonly DbFixture _fixture;
public MyTests(DbFixture f) => _fixture = f; // shared
public void Dispose() { /* per-test cleanup */ }
}
Run Tests
dotnet test
dotnet test --filter "Category=Unit"
dotnet test --collect:"XPlat Code Coverage"