1
resposta

[Dúvida] Esclarecimento sobre UnitTest, WidgetTest e IntegrationTest.

Olá pessoal!

Durante a aula de testes de widget, foi mostrado que é possível validar atualizações de estado do ViewModel
diretamente nos testes de widget. Porém, ao praticar, percebi que essa abordagem tem uma limitação importante que vale
a pena compartilhar.

Quando usamos Mock implements ViewModel (com Mocktail), o mock intercepta os métodos addListener e removeListener, do ChangeNotifier, via noSuchMethod, transformando-os em no-ops. Na prática, isso significa que o widget nunca reconstrói quando o estado do mock muda — os listeners simplesmente não são registrados.

Por isso, tentar validar a UI após uma interação (ex: pressionar "Iniciar" e verificar se o botão muda para "Parar")
resulta em falso positivo se o expect não estiver sendo aguardado corretamente, ou em falha se estiver.

A abordagem mais adequada em testes de widget com mocks é separar as responsabilidades em dois tipos de teste:

  • Teste de estado: pré-configura o mock com o estado desejado (isPlaying: true) e verifica a UI renderizada
  • Teste de interação: executa a ação e verifica que o método correto foi chamado (verify)

Para validar o comportamento real após a interação (o que o ViewModel faz de fato), o lugar correto é o teste de
unidade do ViewModel ou um teste integrado.

Ficou a dúvida: a intenção da aula era mostrar essa limitação como aprendizado, ou existe uma forma recomendada de
contornar isso mantendo tudo no teste de widget?

1 resposta

Oii, Pedro!

Que excelente observação você trouxe. Esse é um ponto que costuma gerar bastante confusão quando começamos a trabalhar com Mocks e o ciclo de vida do Flutter.

Sua análise técnica está corretíssima: ao usar o Mocktail (ou Mockito) para criar um mock de um ChangeNotifier, você está substituindo a implementação real dos métodos. Como o addListener e o removeListener são métodos da classe ChangeNotifier, o Mock os transforma em operações vazias, a menos que você configure o comportamento deles manualmente.

Para deixar o entendimento bem nítido para todos, vamos dividir os tipos de testes e como lidar com essa situação.

A pirâmide de testes no flutter

No Flutter, trabalhamos com três camadas principais, e entender o papel de cada uma ajuda a decidir onde validar cada comportamento:

1. Unit test (Teste de unidade)

Aqui o foco é a lógica de negócio pura. No seu exemplo, o lugar ideal para testar se o método iniciar() altera a variável isPlaying para true e dispara o notifyListeners() é aqui. Você testa o ViewModel isolado, sem nenhuma interface gráfica.

2. Widget test (Teste de componente)

O objetivo é garantir que o Widget renderiza o que deveria e que ele envia os comandos corretos para o ViewModel.

  • A limitação que você encontrou: Como o Mock não dispara notificações reais, o Widget não "sabe" que precisa reconstruir.
  • A boa prática: Como você bem pontuou, fazemos o Setup do Estado. Configuramos o Mock para retornar isPlaying = true e verificamos se o botão de "Parar" aparece. Depois, em outro teste, simulamos o clique e usamos o verify(() => mock.iniciar()).called(1) para garantir que o Widget tentou falar com o ViewModel.

3. Integration test (Teste de integração)

Aqui não usamos Mocks para as peças principais da aplicação. Testamos o fluxo completo: o clique no botão realmente altera o estado do ViewModel real, que por sua vez reconstrói a tela. É o teste que simula o comportamento do usuário final no dispositivo ou simulador.

Existe uma forma de contornar isso no Widget Test?

Sim, existem dois caminhos principais se você quiser que o Widget reaja durante o teste:

  1. Não usar Mock para o ViewModel: Em vez de um Mock, você pode usar um Fake ou a própria instância real do ViewModel (se ela não depender de APIs externas como banco de dados ou internet). Isso permite que o ChangeNotifier funcione naturalmente.
  2. Manual Notification: Se você realmente precisar usar Mock, pode capturar o listener registrado no mock (usando captureAny) e chamá-lo manualmente dentro do teste, mas isso torna o código do teste muito complexo e difícil de ler.

Conclusão

A intenção pedagógica geralmente é mostrar como isolar as partes. Em testes de widget, o contrato é: "Dado que o estado é X, o widget mostra Y" e "Se o usuário fizer Z, o widget chama a função W".

Validar que "Ao clicar em Z, o estado muda para X e então a tela mostra Y" em um único teste de widget usando Mocks é tentar forçar o Mock a ser algo que ele não é (uma implementação real). Sua estratégia de separar em Teste de Estado e Teste de Interação é a abordagem profissional recomendada para manter os testes rápidos e confiáveis.

Continue com esse olhar crítico, pois entender o comportamento das ferramentas de Mock é o que diferencia um desenvolvedor que apenas escreve testes de um que realmente entende a arquitetura da aplicação.

Alura Conte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!