Einleitung: Warum Tests auf fortgeschrittenem Niveau schreiben?
Die Anfangsstufe des Testens in C# beschränkt sich normalerweise auf einfache Assert.Equal und isolierte Unit-Tests. In realen Enterprise-Projekten reicht dies jedoch nicht aus. Fehler treten oft an den Schnittstellen von Komponenten auf – bei der Interaktion mit Datenbanken, externen APIs oder in komplexer Geschäftslogik mit Seiteneffekten.
Fortgeschrittenes Testen in C# (.NET) bedeutet, eine Testpyramide aufzubauen, bei der Unit-Tests schnell und isoliert sind, Integrationstests reale Interaktionen prüfen und E2E-Tests das Benutzerverhalten simulieren. In diesem Artikel betrachten wir moderne Werkzeuge: xUnit, Moq, FluentAssertions, Testcontainers und WebApplicationFactory. Sie erfahren, wie Sie zuverlässige Tests schreiben, die beim Refactoring nicht kaputtgehen und Vertrauen in den Code geben.
1. Fortgeschrittene Unit-Tests: Mocks und Stub-Objekte
Unit-Tests sollten von externen Abhängigkeiten isoliert sein. Auf fortgeschrittenem Niveau verwenden wir Bibliotheken zum Erstellen von Mock-Objekten, wie Moq oder NSubstitute.
Richtige Einrichtung von Mock-Objekten
Anstatt Stubs manuell zu erstellen, verwenden Sie Mock.Of<T>() für eine schnelle Einrichtung. Beispiel mit Moq und FluentAssertions:
public interface IEmailService{ Task SendAsync(string to, string subject);}
public class NotificationService{ private readonly IEmailService _emailService;
public NotificationService(IEmailService emailService) { _emailService = emailService; }
public async Task NotifyUserAsync(string email) { await _emailService.SendAsync(email, "Welcome!"); }}
// Test[Fact]public async Task NotifyUserAsync_ShouldSendEmail(){ // Arrange var mockEmailService = new Mock<IEmailService>(); var service = new NotificationService(mockEmailService.Object);
// Act await service.NotifyUserAsync("test@example.com");
// Assert mockEmailService.Verify( x => x.SendAsync("test@example.com", "Welcome!"), Times.Once);}Vermeiden Sie das Testen von Implementierungsdetails
Ein fortgeschrittener Ansatz ist das Testen des Verhaltens, nicht der internen Aufrufe. Verwenden Sie Verify nur für kritische Abhängigkeiten. Für andere prüfen Sie das Ergebnis.
2. Integrationstests: Datenbank und externe Dienste
Integrationstests prüfen, ob mehrere Module zusammenarbeiten. Für .NET Core/5+ ist das ideale Werkzeug WebApplicationFactory (für ASP.NET Core) und Testcontainers (für die Containerisierung von Abhängigkeiten).
Einrichtung von WebApplicationFactory mit einer echten Datenbank
Anstatt den InMemory-Anbieter zu verwenden (der sich anders verhält als eine echte Datenbank), nutzen Sie Testcontainers zum Starten von PostgreSQL oder SQL Server in Docker:
public class IntegrationTestFixture : IAsyncLifetime{ private readonly TestcontainersContainer _dbContainer;
public string ConnectionString { get; private set; }
public IntegrationTestFixture() { _dbContainer = new TestcontainersBuilder<TestcontainersContainer>() .WithImage("postgres:15") .WithPortBinding(5432, true) .WithEnvironment("POSTGRES_USER", "test") .WithEnvironment("POSTGRES_PASSWORD", "test") .WithEnvironment("POSTGRES_DB", "testdb") .Build(); }
public async Task InitializeAsync() { await _dbContainer.StartAsync(); ConnectionString = $"Host=localhost;Port={_dbContainer.GetMappedPublicPort(5432)};" + $"Database=testdb;Username=test;Password=test"; }
public async Task DisposeAsync() { await _dbConta