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:
- 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. - 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.
Conte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!