Use cargo-insta quando o resultado correto de um teste é uma saída estruturada grande demais para manter em vários assert_eq!, mas ainda pequena o bastante para uma pessoa revisar. A crate insta captura texto, valores de Debug, JSON, YAML e outras representações; a CLI cargo-insta mostra o diff e permite aceitar ou rejeitar a mudança de forma consciente.
O fluxo mínimo é:
cargo add --dev insta
cargo install cargo-insta --locked
cargo insta test
cargo insta review
Snapshot testing funciona especialmente bem para parsers, compiladores, CLIs, geradores de código, mensagens de erro, APIs serializadas e interfaces de terminal. Ele não substitui assertions precisas para regras simples. A pergunta útil é: o snapshot torna a mudança mais fácil de revisar ou apenas esconde uma asserção que deveria ser explícita?
Resposta rápida: cargo-insta na prática
| Objetivo | Ferramenta ou comando |
|---|---|
| Comparar texto | insta::assert_snapshot! |
Comparar Debug | insta::assert_debug_snapshot! |
| Comparar JSON | insta::assert_json_snapshot! |
| Comparar YAML | insta::assert_yaml_snapshot! |
| Rodar os testes | cargo insta test |
| Revisar diferenças | cargo insta review |
| Aceitar pendências | cargo insta accept |
| Rejeitar pendências | cargo insta reject |
| Validar na CI | cargo insta test --check |
| Guardar snapshot no código | inline snapshot com @"..." |
Consulte cargo insta --help e a documentação da versão instalada antes de fixar comandos em uma CI crítica. O comportamento de features, formatos e flags pode evoluir.
O que é snapshot testing
Um teste tradicional compara valores escritos à mão:
#[test]
fn soma_dois_valores() {
assert_eq!(2 + 3, 5);
}
Isso é ideal porque o contrato cabe em uma linha. Agora imagine um parser que produz uma árvore com dezenas de campos:
#[derive(Debug, PartialEq)]
struct Comando {
nome: String,
argumentos: Vec<String>,
redirecionamento: Option<String>,
}
Você poderia construir toda a estrutura esperada no teste. Em alguns casos, essa precisão é desejável. Em outros, a representação Debug já é a forma mais legível de revisar o resultado completo:
#[test]
fn interpreta_comando_com_argumentos() {
let comando = parse("deploy producao --dry-run").unwrap();
insta::assert_debug_snapshot!(comando);
}
Na primeira execução, o insta cria uma referência pendente. Depois da revisão, o snapshot aceito fica versionado junto do projeto. Nas próximas execuções, qualquer diferença produz um diff.
O snapshot não decide se a mudança está correta. Ele torna a mudança visível. A qualidade ainda depende de alguém entender o contrato e revisar o diff.
Instalando a crate e a CLI
Adicione a biblioteca como dependência de desenvolvimento:
[dev-dependencies]
insta = "1"
Para snapshots serializados, habilite apenas os formatos necessários:
[dev-dependencies]
insta = { version = "1", features = ["json", "yaml", "redactions"] }
serde = { version = "1", features = ["derive"] }
Instale a interface interativa:
cargo install cargo-insta --locked
cargo insta --version
cargo insta --help
A separação é importante:
instaé a crate usada pelos testes;cargo-instaé a ferramenta que executa e revisa snapshots;- os snapshots aceitos fazem parte do código e devem entrar no Git;
- arquivos pendentes não devem ser aceitos cegamente.
Primeiro teste com assert_snapshot!
Comece com uma função que gera texto:
fn renderizar_usuario(nome: &str, ativo: bool) -> String {
format!(
"Usuário: {nome}\nStatus: {}",
if ativo { "ativo" } else { "inativo" }
)
}
#[test]
fn renderiza_usuario_ativo() {
insta::assert_snapshot!(renderizar_usuario("Ana", true));
}
Execute:
cargo insta test
cargo insta review
A revisão deve mostrar algo equivalente a:
Usuário: Ana
Status: ativo
Aceite se esse texto representa o contrato desejado. Depois, altere Status para Situação no código e rode novamente. O teste falhará, e o review mostrará exatamente a linha modificada.
Essa ergonomia é excelente para mensagens multilinha. Para um único booleano, número ou enum, prefira assert_eq!: o contrato fica mais explícito e o teste não precisa de arquivo auxiliar.
Snapshots de valores com Debug
assert_debug_snapshot! é útil para ASTs, configurações normalizadas, planos de execução e estruturas internas estáveis:
#[derive(Debug)]
struct Configuracao {
ambiente: String,
porta: u16,
features: Vec<String>,
}
#[test]
fn normaliza_configuracao_de_producao() {
let config = Configuracao {
ambiente: "producao".into(),
porta: 443,
features: vec!["metrics".into(), "tracing".into()],
};
insta::assert_debug_snapshot!(config);
}
Tenha cuidado ao derivar Debug sobre tipos que contêm:
- tokens, senhas ou chaves;
- caminhos absolutos da máquina;
- endereços de memória;
- ordem não determinística de mapas;
- timestamps e identificadores aleatórios.
Snapshots são arquivos versionados. Nunca deixe um segredo entrar neles. Para dados sensíveis, normalize o valor antes do assert ou crie uma representação de teste que exponha apenas campos seguros.
JSON, YAML e dados serializados
Para uma resposta de API, comparar a serialização estruturada costuma produzir um diff melhor do que comparar uma string JSON formatada manualmente.
use serde::Serialize;
#[derive(Serialize)]
struct RespostaUsuario {
id: u64,
nome: String,
papeis: Vec<String>,
}
#[test]
fn serializa_resposta_publica() {
let resposta = RespostaUsuario {
id: 42,
nome: "Marina".into(),
papeis: vec!["admin".into(), "financeiro".into()],
};
insta::assert_json_snapshot!(resposta);
}
O mesmo princípio vale para YAML:
insta::assert_yaml_snapshot!(resposta);
Use o formato que o consumidor realmente enxerga. Se a API pública é JSON, um snapshot JSON ajuda a detectar remoção de campo, mudança de nome e alteração de estrutura. Se o valor é apenas interno, Debug pode ser suficiente e mais barato.
Para entender o contrato de serialização por trás desses testes, veja o guia de Serde em Rust. Em APIs, combine snapshots com testes de status HTTP, headers e erros usando Axum ou o framework adotado pelo projeto.
Redactions: removendo valores instáveis
Snapshots perdem valor quando mudam a cada execução. IDs aleatórios, datas, durações e paths temporários geram ruído e ensinam a equipe a aceitar diffs sem ler.
Com a feature redactions, snapshots serializados podem substituir campos instáveis por marcadores previsíveis. Um exemplo conceitual:
use serde::Serialize;
#[derive(Serialize)]
struct Evento {
id: String,
criado_em: String,
tipo: String,
}
#[test]
fn evento_publicado() {
let evento = Evento {
id: "evt_8f12a9".into(),
criado_em: "2026-08-19T03:00:00Z".into(),
tipo: "pedido.criado".into(),
};
insta::assert_json_snapshot!(evento, {
".id" => "[id]",
".criado_em" => "[timestamp]"
});
}
O snapshot preserva o campo tipo, que faz parte do comportamento relevante, e estabiliza os valores que variam por execução.
Redaction não deve esconder tudo. Se o formato do ID ou a precisão do timestamp são parte do contrato, escreva assertions separadas para esses aspectos antes de substituir o valor no snapshot:
assert!(evento.id.starts_with("evt_"));
assert!(evento.criado_em.ends_with('Z'));
A combinação é melhor do que qualquer extremo: assertions específicas para invariantes pequenas e snapshot para a forma geral.
Inline snapshots
Snapshots inline ficam no próprio arquivo Rust:
#[test]
fn formata_erro_de_validacao() {
let mensagem = "campo email: formato inválido";
insta::assert_snapshot!(mensagem, @"campo email: formato inválido");
}
Eles são úteis quando:
- a saída possui poucas linhas;
- manter expectativa e teste juntos melhora a leitura;
- o diff no arquivo-fonte é mais conveniente;
- você não quer abrir um arquivo de snapshot separado para um caso pequeno.
Para resultados grandes, o inline snapshot polui o teste e dificulta a navegação. Prefira arquivos externos quando a referência tiver dezenas ou centenas de linhas.
A CLI consegue atualizar snapshots inline durante o fluxo de revisão. Como isso modifica código-fonte, revise o diff com o mesmo cuidado aplicado a qualquer alteração de Rust.
Como funciona o review
O comando central não é accept; é review:
cargo insta test
cargo insta review
Durante a revisão, classifique cada diferença:
- mudança intencional: o contrato evoluiu e o snapshot deve ser atualizado;
- regressão: o resultado mudou sem intenção; corrija o código;
- ruído instável: normalize ou aplique redaction;
- snapshot amplo demais: divida o teste;
- assertion errada: substitua o snapshot por uma verificação mais específica.
Os comandos em lote existem:
cargo insta accept
cargo insta reject
Mas a existência de accept não significa que “aceitar tudo” seja um workflow saudável. Um diretório inteiro atualizado depois de um refactor pode conter uma mudança legítima e duas regressões escondidas.
Quando snapshots funcionam melhor
Parsers e compiladores
Uma entrada curta pode gerar tokens, AST, diagnósticos ou código expandido. Snapshots tornam a árvore e as mensagens revisáveis. Combine esse fluxo com cargo-expand quando proc-macros fazem parte da geração.
CLIs
Help text, mensagens de erro e saída tabular são bons candidatos. O guia de CLI profissional em Rust mostra outros contratos que também precisam de testes: exit code, stderr, arquivos criados e compatibilidade de flags.
Interfaces de terminal
Buffers renderizados pelo Ratatui podem ser transformados em texto estável. Isso protege regressões visuais sem depender de um terminal real; veja o guia de TUI com Ratatui e Crossterm.
APIs serializadas
Snapshots de JSON ajudam a revisar contratos maiores. Ainda assim, status, autenticação, paginação e headers merecem assertions próprias.
Mensagens de erro
São úteis quando o texto faz parte da experiência pública. Se a mensagem é interna e muda com frequência, talvez seja melhor verificar a variante do erro e apenas um trecho essencial.
Quando não usar snapshot
Evite snapshot como primeira escolha para:
- cálculo com resultado simples;
- autorização booleana;
- regra de limite;
- ordenação com poucos itens;
- transição de estado pequena;
- invariantes que cabem em
assert_eq!; - comportamento que deve valer para muitas entradas.
Exemplo ruim:
insta::assert_debug_snapshot!(pode_sacar(100, 40));
Exemplo melhor:
assert!(pode_sacar(100, 40));
assert!(pode_sacar(100, 100));
assert!(!pode_sacar(100, 101));
Para invariantes sobre uma faixa grande de valores, use property-based testing. O guia de proptest e fuzzing cobre essa estratégia. Para verificar se assertions realmente detectam defeitos, use cargo-mutants.
Evitando snapshots gigantes e frágeis
Um snapshot de 5.000 linhas raramente é revisado de verdade. Reduza o escopo:
- teste um módulo ou componente por vez;
- serialize apenas os campos públicos relevantes;
- separe caminho feliz e erros;
- ordene coleções antes de capturá-las;
- remova valores ambientais;
- prefira múltiplos snapshots pequenos a um dump total do sistema;
- dê nomes claros aos testes e snapshots.
Também evite acoplar a expectativa a detalhes de implementação. Um snapshot de toda a estrutura privada pode quebrar em um refactor que não muda o comportamento público. Quando isso acontece com frequência, crie uma visão estável para teste:
#[derive(serde::Serialize)]
struct PlanoPublico<'a> {
etapas: &'a [String],
paralelismo: usize,
}
Capture PlanoPublico, não caches, ponteiros e campos temporários do executor.
cargo-insta na CI
A CI deve verificar, não aprovar. Um job simples:
name: snapshots
on:
pull_request:
push:
branches: [main]
jobs:
insta:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Instalar cargo-insta
run: cargo install cargo-insta --locked
- name: Verificar snapshots
run: cargo insta test --check
Em uma esteira real:
- fixe uma versão homologada da CLI;
- versione os snapshots aceitos;
- falhe se houver snapshots novos, alterados ou pendentes;
- não rode
cargo insta acceptautomaticamente; - mantenha testes de documentação se o runner não os executar;
- preserve o diff como log ou artefato quando ele ajudar a investigação.
Para suítes grandes, o cargo-nextest melhora paralelismo, filtros e retries. Confirme na versão instalada como o cargo-insta deve invocar o runner adotado pelo projeto. A regra é comparar a seleção de testes antes de trocar um comando obrigatório.
Snapshot testing, cobertura e mutação
As ferramentas respondem perguntas diferentes:
| Técnica | Pergunta |
|---|---|
| Assertion tradicional | este valor específico está correto? |
| Snapshot | esta saída ampla mudou? |
| cargo-llvm-cov | quais regiões foram executadas? |
| cargo-mutants | os testes percebem alterações plausíveis? |
| proptest | a propriedade vale para muitas entradas? |
| fuzzing | entradas inesperadas causam falha ou violação? |
Um projeto pode ter snapshots atualizados e ainda não testar um branch crítico. Pode ter cobertura alta e snapshots frágeis. Pode matar mutantes em regras de domínio, mas deixar o contrato JSON mudar sem revisão.
Use cargo-llvm-cov para localizar caminhos não exercitados e cargo-insta para revisar outputs complexos já alcançados. Use cada técnica pelo sinal que ela oferece.
Armadilhas comuns
Aceitar snapshots sem ler
É a falha mais perigosa. O teste vira um gerador de arquivos, não uma proteção. Revise causa e efeito.
Capturar dados não determinísticos
HashMaps, clocks, UUIDs, paths e concorrência podem mudar entre execuções. Ordene, normalize ou use redactions.
Versionar segredos
Snapshots entram no Git. Use dados fictícios e revise dumps antes do commit.
Testar detalhes privados demais
Refactors legítimos passam a quebrar dezenas de arquivos. Capture a representação pública ou um modelo estável de teste.
Criar snapshots enormes
Quanto maior o diff, menor a chance de revisão cuidadosa. Divida por comportamento.
Substituir toda assertion por snapshot
assert_eq!(status, Status::Pago) comunica mais do que um arquivo com o debug completo de um pedido.
Esquecer arquivos pendentes
A CI precisa falhar quando existem novas referências não revisadas. O repositório deve conter apenas o estado aceito e intencional.
Checklist de adoção
- adicionar
instaem[dev-dependencies]; - habilitar somente features necessárias;
- instalar
cargo-instacom versão controlada; - começar por uma saída estruturada realmente difícil de afirmar à mão;
- executar
cargo insta teste revisar comcargo insta review; - versionar snapshots aceitos;
- remover timestamps, UUIDs, paths e ordem instável;
- usar redactions sem esconder invariantes relevantes;
- preferir inline snapshots apenas para saídas pequenas;
- manter assertions tradicionais para regras simples;
- limitar tamanho e escopo de cada snapshot;
- impedir aceitação automática na CI;
- revisar snapshots como código em pull requests;
- combinar snapshots com cobertura, mutação e propriedades quando necessário.
cargo-insta para carreira e portfólio Rust
Snapshot testing demonstra maturidade quando o README explica por que a técnica foi escolhida. Um projeto de portfólio pode mostrar:
- parser com snapshots de AST e diagnósticos;
- CLI com snapshots de help e erros, além de exit codes explícitos;
- API com snapshots JSON e redactions de IDs;
- TUI com snapshots de buffer em diferentes tamanhos;
- CI que rejeita referências pendentes;
- pull request de exemplo com um diff revisado.
Isso é mais convincente do que afirmar “possui testes”. Você mostra que sabe escolher entre assertions precisas, snapshots, propriedades, cobertura e mutação.
Esse repertório aparece em vagas Rust para bibliotecas, tooling, backend, compiladores, developer experience e sistemas. Para montar a base, estude testes em Rust, o guia de estratégias de teste e CI/CD em Rust.
Perguntas frequentes
O que é cargo-insta?
cargo-insta é a CLI que acompanha o ecossistema da crate insta. Ela executa o fluxo de snapshots e apresenta diferenças para revisão, aceitação ou rejeição. A biblioteca fica nas dependências de desenvolvimento; a CLI organiza o trabalho diário.
Como começar?
cargo add --dev insta
cargo install cargo-insta --locked
cargo insta test
cargo insta review
Para JSON ou YAML, habilite as features correspondentes no Cargo.toml.
Snapshot testing substitui assert_eq!?
Não. Use snapshots para resultados amplos e legíveis; use assert_eq! e assertions específicas para invariantes pequenas. Muitas vezes o melhor teste combina os dois.
Como evitar testes frágeis?
Mantenha o snapshot pequeno, ordene coleções, normalize ambiente, aplique redactions e capture uma representação pública estável. Se um refactor interno quebra tudo sem mudar comportamento, o snapshot está acoplado demais.
Como usar na CI?
Versione as referências aceitas e execute cargo insta test --check. A CI deve falhar diante de diferenças, nunca aceitá-las automaticamente.
Conclusão
cargo-insta transforma outputs complexos em diffs revisáveis. Para parsers, CLIs, JSON, mensagens de erro e TUIs, isso pode deixar testes mais claros e mudanças de contrato muito mais visíveis do que blocos enormes de assertions manuais.
O valor não está em gerar arquivos automaticamente. Está no ciclo: capturar uma saída estável, revisar a diferença, aceitar apenas mudanças intencionais e manter o snapshot pequeno o bastante para continuar legível.
Comece por um único caso em que a expectativa já é trabalhosa. Instale insta, rode cargo insta test, revise com cargo insta review e coloque --check na CI. Depois conecte snapshots a uma estratégia mais ampla com cargo-nextest, cargo-llvm-cov, cargo-mutants e proptest. Assim, snapshots protegem contratos visíveis sem virar uma coleção de arquivos que ninguém entende.