Snapshot-Testing mit Verify
Wann diese Fähigkeit einsetzen
Verwenden Sie Snapshot-Testing, wenn Sie:
- Gerenderte Ausgaben überprüfen (HTML-E-Mails, Berichte, generierten Code)
- Öffentliche API-Oberflächen zur Erkennung von Breaking Changes genehmigen
- HTTP-Antwortkörper und -Header testen
- Serialisierungsausgabe validieren
- Unbeabsichtigte Änderungen in komplexen Objekten abfangen
Was ist Snapshot-Testing?
Snapshot-Testing erfasst die Ausgabe und vergleicht sie mit einer von Menschen genehmigten Basisdatei:
- Erster Lauf: Der Test generiert eine
.received.-Datei mit der tatsächlichen Ausgabe - Manuelle Überprüfung: Der Entwickler genehmigt sie und erstellt eine
.verified.-Datei - Folgende Läufe: Der Test vergleicht die Ausgabe mit der
.verified.-Datei - Änderungen erkannt: Der Test schlägt fehl, und ein Diff-Tool zeigt die Unterschiede zur Überprüfung an
Dies fängt unbeabsichtigte Änderungen ab, während beabsichtigte Änderungen durch explizite Genehmigung möglich sind.
Installation
Verify-Paket hinzufügen
dotnet add package Verify.Xunit
# oder für andere Testframeworks:
dotnet add package Verify.NUnit
dotnet add package Verify.MSTest
ModuleInitializer konfigurieren
Erstellen Sie eine ModuleInitializer.cs in Ihrem Testprojekt:
using System.Runtime.CompilerServices;
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Init()
{
// Verwenden Sie quelltextdatei-relativen Pfad für verified-Dateien
VerifyBase.UseProjectRelativeDirectory("Snapshots");
// Diff-Tool konfigurieren (optional - automatisch erkannt)
// DiffTools.UseOrder(DiffTool.Rider, DiffTool.VisualStudioCode);
}
}
Grundlegende Verwendung
Einfache Objektüberprüfung
[Fact]
public Task VerifyUserDto()
{
var user = new UserDto(
Id: "user-123",
Name: "John Doe",
Email: "[email protected]",
CreatedAt: new DateTime(2025, 1, 15));
return Verify(user);
}
Erstellt VerifyUserDto.verified.txt:
{
Id: user-123,
Name: John Doe,
Email: [email protected],
CreatedAt: 2025-01-15T00:00:00
}
String-/HTML-Überprüfung
[Fact]
public async Task VerifyRenderedEmail()
{
var html = await _emailRenderer.RenderAsync("Welcome", new { Name = "John" });
// Verwenden Sie den extension-Parameter für korrekte Dateibenennung
await Verify(html, extension: "html");
}
Erstellt VerifyRenderedEmail.verified.html – im Browser anzeigbar.
E-Mail-Vorlagentests
Verwenden Sie Verify, um unbeabsichtigte Änderungen in gerenderten E-Mail-Vorlagen abzufangen:
[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();
var variables = new Dictionary<string, string>
{
{ "OrganizationName", "Acme Corporation" },
{ "InviteeName", "John Doe" },
{ "InviterName", "Jane Admin" },
{ "InvitationLink", "https://example.com/invite/abc123" },
{ "ExpirationDate", "December 31, 2025" }
};
var html = await renderer.RenderTemplateAsync(
"UserInvitations/UserSignupInvitation",
variables);
await Verify(html, extension: "html");
}
Vorteile für E-Mail-Tests:
- Fängt CSS-/Layout-Regressionen ab
- Erkennt defekte Vorlagenvariablen
- Visuelle Überprüfung im Diff-Tool
- Versionskontrolle verfolgt E-Mail-Änderungen
Genehmigung von API-Oberflächen
Verhindern Sie unbeabsichtigte Breaking Changes an öffentlichen APIs:
[Fact]
public Task ApprovePublicApi()
{
var assembly = typeof(MyLibrary.PublicClass).Assembly;
var publicApi = assembly.GetExportedTypes()
.OrderBy(t => t.FullName)
.Select(t => new
{
Type = t.FullName,
Members = t.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
.Where(m => m.DeclaringType == t)
.OrderBy(m => m.Name)
.Select(m => m.ToString())
});
return Verify(publicApi);
}
Oder verwenden Sie das dedizierte ApiApprover-Paket:
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApproveApi()
{
var api = typeof(MyPublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}
Erstellt .verified.txt mit der vollständigen API-Oberfläche – jede Änderung erfordert explizite Genehmigung.
HTTP-Antworttests
[Fact]
public async Task GetUser_ReturnsExpectedResponse()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/users/123");
// Status, Header und Body zusammen überprüfen
await Verify(new
{
StatusCode = response.StatusCode,
Headers = response.Headers
.Where(h => h.Key.StartsWith("X-")) // Nur benutzerdefinierte Header
.ToDictionary(h => h.Key, h => h.Value.First()),
Body = await response.Content.ReadAsStringAsync()
});
}
Herausfiltern dynamischer Werte
Behandeln Sie Zeitstempel, GUIDs und andere dynamische Inhalte:
[Fact]
public Task VerifyOrder()
{
var order = new Order
{
Id = Guid.NewGuid(), // Bei jedem Lauf unterschiedlich
CreatedAt = DateTime.UtcNow, // Bei jedem Lauf unterschiedlich
Total = 99.99m
};
return Verify(order)
.ScrubMember("Id") // Durch Platzhalter ersetzen
.ScrubMember("CreatedAt");
}
Ausgabe:
{
Id: Guid_1,
CreatedAt: DateTime_1,
Total: 99.99
}
Globales Herausfiltern
Konfigurieren Sie in ModuleInitializer:
[ModuleInitializer]
public static void Init()
{
VerifierSettings.ScrubMembersWithType<DateTime>();
VerifierSettings.ScrubMembersWithType<DateTimeOffset>();
VerifierSettings.ScrubMembersWithType<Guid>();
// Bestimmte Muster herausfiltern
VerifierSettings.AddScrubber(s =>
Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"));
}
Dateiorganisation
Empfohlene Struktur
tests/
MyApp.Tests/
Snapshots/ # Alle verified-Dateien
EmailTests/
WelcomeEmail.verified.html
PasswordReset.verified.html
ApiTests/
GetUser.verified.txt
EmailTests.cs
ApiTests.cs
ModuleInitializer.cs
.gitignore
# Verify - received-Dateien ignorieren (nur verified committen)
*.received.*
.gitattributes
# verified-Dateien als generiert behandeln (in PR-Diffs einklappen)
*.verified.txt linguist-generated=true
*.verified.html linguist-generated=true
*.verified.json linguist-generated=true
CI/CD-Integration
Fehlschlagen bei fehlenden verified-Dateien
[ModuleInitializer]
public static void Init()
{
// In CI fehlschlagen statt Diff-Tool zu starten
if (Environment.GetEnvironmentVariable("CI") == "true")
{
VerifyDiffPlex.UseDiffPlex(OutputType.Minimal);
DiffRunner.Disabled = true;
}
}
GitHub Actions
- name: Run tests
run: dotnet test
env:
CI: true
- name: Upload snapshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: snapshots
path: |
**/*.received.*
**/*.verified.*
Wann Snapshot-Testing verwenden
| Szenario | Snapshot-Testing verwenden? | Warum |
|---|---|---|
| Gerendertes HTML/E-Mails | Ja | Erfasst visuelle Regressionen |
| API-Oberflächen | Ja | Verhindert unbeabsichtigte Änderungen |
| Serialisierungsausgabe | Ja | Validiert das Übertragungsformat |
| Komplexe Objektgraphen | Ja | Einfacher als manuelle Asserts |
| Einfache Wertprüfungen | Nein | Normale Asserts verwenden |
| Geschäftslogik | Nein | Explizite Asserts verwenden |
| Leistungstests | Nein | Benchmarks verwenden |
Best Practices
DO
// Aussagekräftige Testnamen verwenden - sie werden Dateinamen
[Fact]
public Task UserRegistration_WithValidData_ReturnsConfirmation()
// Dynamische Werte konsistent herausfiltern
VerifierSettings.ScrubMembersWithType<Guid>();
// extension-Parameter für Nicht-Text-Inhalte verwenden
await Verify(html, extension: "html");
// verified-Dateien in der Versionskontrolle behalten
git add *.verified.*
DON'T
// Keine zufälligen/dynamischen Daten ohne Herausfiltern überprüfen
var order = new Order { Id = Guid.NewGuid() }; // Fehlschlag bei jedem Lauf!
await Verify(order);
// Keine .received-Dateien committen
git add *.received.* // Falsch!
// Nicht für einfache Asserts verwenden
await Verify(result.Count); // Einfach Assert.Equal(5, result.Count) verwenden
Integration mit MJML-E-Mail-Tests
Siehe die Fähigkeit aspnetcore/transactional-emails für das vollständige Muster:
- MJML-Vorlagen mit
{{variable}}-Platzhaltern - Rendern zu HTML mit Testdaten
- Snapshot-Test der gerenderten Ausgabe
- Änderungen vor der Genehmigung im Diff-Tool überprüfen
Dies erfasst:
- Fehlerhafte Variablensubstitution
- CSS-/Layout-Regressionen
- Kompatibilitätsprobleme mit E-Mail-Clients
- Unbeabsichtigte Inhaltsänderungen
Ressourcen
- Verify GitHub: https://github.com/VerifyTests/Verify
- Verify.Xunit: https://github.com/VerifyTests/Verify.Xunit
- ApiApprover: https://github.com/JakeGinnivan/ApiApprover
- DiffPlex Integration: https://github.com/VerifyTests/Verify.DiffPlex