O que Testes de Snapshot com Verify faz?
Quando Usar Esta Skill
Use testes de snapshot quando:
- Verificando saída renderizada (e-mails HTML, relatórios, código gerado)
- Aprovando superfícies de API públicas para detecção de mudanças de quebra
- Testando corpos e cabeçalhos de respostas HTTP
- Validando saída de serialização
- Capturando alterações indesejadas em objetos complexos
O que é Teste de Snapshot?
Teste de snapshot captura a saída e a compara com uma linha de base aprovada por humano:
- Primeira execução: O teste gera um arquivo
.received.com a saída real - Revisão humana: O desenvolvedor aprova, criando um arquivo
.verified. - Execuções subsequentes: O teste compara a saída com o arquivo
.verified. - Mudanças detectadas: O teste falha, e a ferramenta de diff mostra as diferenças para revisão
Isso captura alterações indesejadas enquanto permite alterações intencionais por meio de aprovação explícita.
Instalação
Adicionar Pacote Verify
dotnet add package Verify.Xunit
# ou para outros frameworks de teste:
dotnet add package Verify.NUnit
dotnet add package Verify.MSTest
Configurar ModuleInitializer
Crie um arquivo ModuleInitializer.cs no seu projeto de teste:
using System.Runtime.CompilerServices;
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Init()
{
// Usar caminhos relativos ao arquivo fonte para arquivos verificados
VerifyBase.UseProjectRelativeDirectory("Snapshots");
// Configurar ferramenta de diff (opcional - detectado automaticamente)
// DiffTools.UseOrder(DiffTool.Rider, DiffTool.VisualStudioCode);
}
}
Uso Básico
Verificação de Objeto Simples
[Fact]
public Task VerifyUserDto()
{
var user = new UserDto(
Id: "user-123",
Name: "João Silva",
Email: "[email protected]",
CreatedAt: new DateTime(2025, 1, 15));
return Verify(user);
}
Cria VerifyUserDto.verified.txt:
{
Id: user-123,
Name: João Silva,
Email: [email protected],
CreatedAt: 2025-01-15T00:00:00
}
Verificação de String/HTML
[Fact]
public async Task VerifyRenderedEmail()
{
var html = await _emailRenderer.RenderAsync("Welcome", new { Name = "João" });
// Usar parâmetro de extensão para nomeação correta de arquivo
await Verify(html, extension: "html");
}
Cria VerifyRenderedEmail.verified.html - visualizável em navegador.
Teste de Template de E-mail
Use Verify para capturar alterações indesejadas em templates de e-mail renderizados:
[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();
var variables = new Dictionary<string, string>
{
{ "OrganizationName", "Acme Corporation" },
{ "InviteeName", "João Silva" },
{ "InviterName", "Maria Admin" },
{ "InvitationLink", "https://exemplo.com/invite/abc123" },
{ "ExpirationDate", "31 de dezembro de 2025" }
};
var html = await renderer.RenderTemplateAsync(
"UserInvitations/UserSignupInvitation",
variables);
await Verify(html, extension: "html");
}
Benefícios para teste de e-mail:
- Captura regressões de CSS/layout
- Detecta quebras em variáveis de template
- Revisão visual na ferramenta de diff
- Controle de versão rastreia alterações de e-mail
Aprovação de Superfície de API
Evite mudanças de quebra acidentais em APIs públicas:
[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);
}
Ou use o pacote dedicado ApiApprover:
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApproveApi()
{
var api = typeof(MyPublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}
Cria .verified.txt com toda a superfície da API - qualquer alteração requer aprovação explícita.
Teste de Resposta HTTP
[Fact]
public async Task GetUser_ReturnsExpectedResponse()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/users/123");
// Verificar status, cabeçalhos e corpo juntos
await Verify(new
{
StatusCode = response.StatusCode,
Headers = response.Headers
.Where(h => h.Key.StartsWith("X-")) // Apenas cabeçalhos personalizados
.ToDictionary(h => h.Key, h => h.Value.First()),
Body = await response.Content.ReadAsStringAsync()
});
}
Limpeza de Valores Dinâmicos
Lide com timestamps, GUIDs e outro conteúdo dinâmico:
[Fact]
public Task VerifyOrder()
{
var order = new Order
{
Id = Guid.NewGuid(), // Diferente a cada execução
CreatedAt = DateTime.UtcNow, // Diferente a cada execução
Total = 99.99m
};
return Verify(order)
.ScrubMember("Id") // Substituir por placeholder
.ScrubMember("CreatedAt");
}
Saída:
{
Id: Guid_1,
CreatedAt: DateTime_1,
Total: 99.99
}
Limpeza Global
Configure em ModuleInitializer:
[ModuleInitializer]
public static void Init()
{
VerifierSettings.ScrubMembersWithType<DateTime>();
VerifierSettings.ScrubMembersWithType<DateTimeOffset>();
VerifierSettings.ScrubMembersWithType<Guid>();
// Limpar padrões específicos
VerifierSettings.AddScrubber(s =>
Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"));
}
Organização de Arquivos
Estrutura Recomendada
tests/
MyApp.Tests/
Snapshots/ # Todos os arquivos verificados
EmailTests/
WelcomeEmail.verified.html
PasswordReset.verified.html
ApiTests/
GetUser.verified.txt
EmailTests.cs
ApiTests.cs
ModuleInitializer.cs
.gitignore
# Verify - ignorar arquivos received (commitar apenas verified)
*.received.*
.gitattributes
# Tratar arquivos verified como gerados (colapsar em diffs de PR)
*.verified.txt linguist-generated=true
*.verified.html linguist-generated=true
*.verified.json linguist-generated=true
Integração com CI/CD
Falhar na Ausência de Arquivos Verified
[ModuleInitializer]
public static void Init()
{
// Em CI, falhar em vez de abrir ferramenta de diff
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.*
Quando Usar Testes de Snapshot
| Cenário | Usar Teste de Snapshot? | Por que? |
|---|---|---|
| HTML/e-mails renderizados | Sim | Captura regressões visuais |
| Superfícies de API | Sim | Previne quebras acidentais |
| Saída de serialização | Sim | Valida formato de transmissão |
| Grafos de objetos complexos | Sim | Mais fácil que asserções manuais |
| Verificações de valor simples | Não | Use asserções regulares |
| Lógica de negócio | Não | Use asserções explícitas |
| Testes de performance | Não | Use benchmarks |
Melhores Práticas
FAÇA
// Use nomes de teste descritivos - eles se tornam nomes de arquivo
[Fact]
public Task UserRegistration_WithValidData_ReturnsConfirmation()
// Limpe valores dinâmicos consistentemente
VerifierSettings.ScrubMembersWithType<Guid>();
// Use parâmetro de extensão para conteúdo não textual
await Verify(html, extension: "html");
// Mantenha arquivos verified no controle de versão
git add *.verified.*
NÃO FAÇA
// Não verifique dados aleatórios/dinâmicos sem limpeza
var order = new Order { Id = Guid.NewGuid() }; // Falha toda execução!
await Verify(order);
// Não commitar arquivos .received
git add *.received.* // Errado!
// Não use para asserções simples
await Verify(result.Count); // Apenas use Assert.Equal(5, result.Count)
Integração com Teste de E-mail MJML
Consulte a skill aspnetcore/transactional-emails para o padrão completo:
- Templates MJML com placeholders
{{variable}} - Renderizar para HTML com dados de teste
- Testar snapshot da saída renderizada
- Revisar alterações na ferramenta de diff antes de aprovar
Isso captura:
- Quebra de substituição de variáveis
- Regressões de CSS/layout
- Problemas de compatibilidade com clientes de e-mail
- Alterações indesejadas de conteúdo
Recursos
- Verify GitHub: https://github.com/VerifyTests/Verify
- Verify.Xunit: https://github.com/VerifyTests/Verify.Xunit
- ApiApprover: https://github.com/JakeGinnivan/ApiApprover
- Integração DiffPlex: https://github.com/VerifyTests/Verify.DiffPlex