Snapshot-Tests für E-Mail-Vorlagen mit Verify
Wann diese Fähigkeit angewendet werden sollte
Verwenden Sie diese Fähigkeit, wenn:
- Sie die Darstellung von E-Mail-Vorlagen auf Regressionen testen möchten
- Sie MJML-Vorlagen validieren möchten, die in erwartetes HTML kompiliert werden
- Sie E-Mail-Änderungen im Code-Review überprüfen (Diffs sind visuell)
- Sie sicherstellen möchten, dass die Variablenersetzung korrekt funktioniert
Verwandte Fähigkeiten:
aspnetcore/mjml-email-templates– MJML-Vorlagenerstellungaspire/mailpit-integration– Testen der E-Mail-Zustellung lokaltesting/snapshot-testing– Allgemeine Verify-Muster
Warum Snapshot-Tests für E-Mails?
E-Mail-Vorlagen sind:
- Visuell – Kleine Änderungen können die Darstellung über verschiedene Clients hinweg beeinträchtigen
- Schwer zu testen – Die Ausgabe ist komplexes HTML, keine einfachen Werte
- Anfällig für Regressionen – Vorlagenänderungen können unerwartete Auswirkungen haben
Snapshot-Tests erfassen das gerenderte HTML und schlagen fehl, wenn es sich unerwartet ändert.
Installation
dotnet add package Verify.Xunit # oder Verify.NUnit, Verify.MSTest
Einfacher E-Mail-Snapshot-Test
[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
// Arrange
var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();
var variables = new Dictionary<string, string>
{
{ "PreviewText", "Sie wurden eingeladen, Acme Corp beizutreten" },
{ "OrganizationName", "Acme Corporation" },
{ "InviteeName", "Max Mustermann" },
{ "InviterName", "Jane Admin" },
{ "InvitationLink", "https://example.com/invite/abc123" },
{ "ExpirationDate", "31. Dezember 2025" }
};
// Act
var html = await renderer.RenderTemplateAsync(
"UserInvitations/UserSignupInvitation",
variables);
// Assert
await Verify(html, extension: "html");
}
Dies erstellt bei der ersten Ausführung UserSignupInvitation_RendersCorrectly.verified.html.
Überprüfen von E-Mail-Änderungen
Wenn eine Vorlage geändert wird, schlägt der Test mit einem Diff fehl. Überprüfungsmöglichkeiten:
1. Visuelles Diff-Tool
# Diff-Tool konfigurieren (einmalig)
dotnet tool install -g verify.tool
verify accept # Alle ausstehenden Änderungen akzeptieren
verify review # Diff-Tool öffnen
2. Browser-Vorschau
Öffnen Sie die Datei .received.html in einem Browser, um die tatsächliche Darstellung zu sehen.
3. IDE-Integration
Die meisten IDEs zeigen Inline-Diffs für .verified.html vs. .received.html Dateien an.
Testen jeder Vorlagenvariante
Erstellen Sie Tests für jede E-Mail-Vorlage, um Regressionen zu erkennen:
public class EmailTemplateSnapshotTests : IClassFixture<EmailTestFixture>
{
private readonly IMjmlTemplateRenderer _renderer;
public EmailTemplateSnapshotTests(EmailTestFixture fixture)
{
_renderer = fixture.Services.GetRequiredService<IMjmlTemplateRenderer>();
}
[Fact]
public async Task WelcomeEmail_NewUser() =>
await VerifyTemplate("Welcome/NewUser", new Dictionary<string, string>
{
{ "UserName", "Max Mustermann" },
{ "LoginUrl", "https://example.com/login" }
});
[Fact]
public async Task WelcomeEmail_InvitedUser() =>
await VerifyTemplate("Welcome/InvitedUser", new Dictionary<string, string>
{
{ "UserName", "Max Mustermann" },
{ "InviterName", "Jane Admin" },
{ "OrganizationName", "Acme Corp" }
});
[Fact]
public async Task PasswordReset() =>
await VerifyTemplate("PasswordReset/PasswordReset", new Dictionary<string, string>
{
{ "UserName", "Max Mustermann" },
{ "ResetLink", "https://example.com/reset/abc123" },
{ "ExpirationMinutes", "30" }
});
[Fact]
public async Task PaymentReceipt() =>
await VerifyTemplate("Billing/PaymentReceipt", new Dictionary<string, string>
{
{ "UserName", "Max Mustermann" },
{ "Amount", "10,00 €" },
{ "InvoiceNumber", "INV-2025-001" },
{ "Date", "15. Januar 2025" }
});
private async Task VerifyTemplate(
string templateName,
Dictionary<string, string> variables)
{
var html = await _renderer.RenderTemplateAsync(templateName, variables);
await Verify(html, extension: "html")
.UseMethodName(templateName.Replace("/", "_"));
}
}
Bereinigen dynamischer Werte
Einige Werte ändern sich zwischen Testläufen. Bereinigen Sie sie:
[Fact]
public async Task EmailWithTimestamp_ScrubsDynamicValues()
{
var html = await _renderer.RenderTemplateAsync("Welcome", variables);
await Verify(html, extension: "html")
.ScrubLinesContaining("Generiert um:")
.ScrubInlineGuids(); // Bereinigt GUIDs in URLs
}
Häufig verwendete Bereiniger
// Daten bereinigen
.ScrubLinesContaining("Datum:")
.AddScrubber(s => Regex.Replace(s, @"\d{4}-\d{2}-\d{2}", "BEREINIGTES-DATUM"))
// URLs mit Tokens bereinigen
.AddScrubber(s => Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=BEREINIGT"))
// GUIDs bereinigen
.ScrubInlineGuids()
Testvorrichtung für E-Mail-Tests
public class EmailTestFixture : IAsyncLifetime
{
public IServiceProvider Services { get; private set; } = null!;
public async Task InitializeAsync()
{
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["SiteUrl"] = "https://example.com"
})
.Build());
services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();
Services = services.BuildServiceProvider();
await Task.CompletedTask;
}
public Task DisposeAsync() => Task.CompletedTask;
}
Composer-Snapshot-Tests
Testen Sie die vollständige Composer-Ausgabe einschließlich Betreff und Metadaten:
[Fact]
public async Task SignupInvitation_ComposesCorrectEmail()
{
var composer = _services.GetRequiredService<IUserEmailComposer>();
var email = await composer.ComposeSignupInvitationAsync(
recipientEmail: new EmailAddress("[email protected]"),
recipientName: new PersonName("Max Mustermann"),
inviterName: new PersonName("Jane Admin"),
organizationName: new OrganizationName("Acme Corp"),
invitationUrl: new AbsoluteUri("https://example.com/invite/abc123"),
expiresAt: new DateTimeOffset(2025, 12, 31, 0, 0, 0, TimeSpan.Zero));
// Überprüfen des vollständigen E-Mail-Objekts (Betreff, An, Text)
await Verify(new
{
email.To,
email.Subject,
HtmlBody = email.HtmlBody // Wird mit der Erweiterung .html gespeichert
});
}
CI-Integration
Fehler bei fehlender Baseline
In CI: Fehler, wenn keine Datei .verified.html vorhanden ist (verhindert versehentliches Akzeptieren):
// In der Testeinrichtung oder ModuleInitializer
VerifierSettings.ThrowOnMissingVerifiedFile();
Git-Konfiguration
Fügen Sie Folgendes zu .gitattributes hinzu, um die Diff-Behandlung zu verbessern:
*.verified.html linguist-language=HTML
*.verified.html diff=html
Best Practices
MACHEN
// MACHEN: Jede Vorlagenvariante testen
[Fact] Task WelcomeEmail_NewUser_RendersCorrectly()
[Fact] Task WelcomeEmail_InvitedUser_RendersCorrectly()
// MACHEN: Beschreibende Testnamen verwenden
[Fact] Task PaymentReceipt_WithRefund_ShowsRefundAmount()
// MACHEN: Dynamische Werte konsistent bereinigen
.ScrubLinesContaining("Generiert um:")
// MACHEN: Diffs sorgfältig überprüfen, bevor sie akzeptiert werden
verify review
NICHT MACHEN
// NICHT MACHEN: E-Mail-Tests überspringen
// NICHT MACHEN: Änderungen ohne Überprüfung automatisch akzeptieren
verify accept --all // Gefährlich!
// NICHT MACHEN: Nur den Happy Path testen
// NICHT MACHEN: Snapshot-Testfehler ignorieren
Arbeitsablauf
- Vorlage erstellen – MJML-Vorlage schreiben
- Test schreiben – Snapshot-Test mit Beispielvariablen hinzufügen
- Test ausführen – Erster Lauf erstellt
.verified.html - Überprüfen – Im Browser öffnen, Darstellung bestätigen
- Einchecken –
.verified.htmlin die Versionskontrolle aufnehmen - Iterieren – Änderungen führen zu Testfehlern, Diff überprüfen, bei Korrektheit akzeptieren
Ressourcen
- Verify: https://github.com/VerifyTests/Verify
- Verify.Xunit: https://github.com/VerifyTests/Verify#xunit
- Diff-Tools: https://github.com/VerifyTests/DiffEngine