Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Dúvida] [TimerViewModel] Dúvidas referente ao Mock e Provider

Olá, tudo bem com vocês?
Primeiramente gostaria de agradecer os conhecimentos compartilhados, mas fiquei cm uma pulga atrás da orelha aqui com alguns testes que realizei, conforme detalhei abaixo:

Estava realizando alguns testes e aparentemente esse teste do TimerWidget não está correto.

A forma correta de utilizar o tester.runAsync é declarando com await no início (Mesmo porque ele é do tupo Future), caso contrário ele irá retornar o teste como executado cm sucesso e apenas depois irá realizar as asserções. Com o await ele executa primeiro. Segue exemplo:

Caso sem await:
Insira aqui a descrição dessa imagem para ajudar na acessibilidade
Note que o teste foi executado com sucesso, porém o console acusa que nenhum Widget com o texto "00:10" foi encontrado.

Caso com o await:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Note que agora sim, o teste falhou por não ter encontrado o Widget com texto "00:10".

Acredito que pelo fato de o método start estar sendo mockado, o timer não inicializa de fato.

Outro teste que realizei sem mock, o timer inicializou corretamente:
caso sem mock:
Insira aqui a descrição dessa imagem para ajudar na acessibilidade
Note que, nesse caso, o timer foi incializado e o display foi atualizado de forma correta e exibiu o texto "00:10", após uma espera de 10 segundos.

Porém o teste só se comporta conforma esperado sem o Provider e sem o mock dos métodos.

O raciocínio procede ou será que cometi algum erro na implementação?

2 respostas

Segue abaixo uma sugestão de implementação que atente todos os caso.

No caso abaixo o que fiz foi realizar a inversão de dependência, porém sem a utilização do provider (Acredito que funcionaria também com o provider), preferi manter sem, por não ver necessiade de utilizá-lo (Não sei se foi intencional a utilização para fins didáticos).

Nosso componente TimerWidget agora passa a recever uma instância de TimerViewModel, via parâmetro do construtor;

  1. Nos cenários de teste onde será testado as chamadas de função, deve-se passar um mock do TimerViewModel
  2. Nos cenários de teste onde será testado o comportamento de Widget, deve-se passar umas instância do TimerViewModel, sem estar mockado.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fokus/app/view_models/timer_view_model.dart';
import 'package:fokus/app/views/widgets/timer_widget.dart';
import 'package:mocktail/mocktail.dart';

class MockTimerViewModel extends Mock implements TimerViewModel {}

void main() {
  final MockTimerViewModel mockTimerViewModel = MockTimerViewModel();
  final TimerViewModel timerViewModel = TimerViewModel();

  Widget createWidget({required TimerViewModel timerViewModel}) {
    return MaterialApp(
      home: Scaffold(
        body: TimerWidget(initialMinutes: 1, timerViewModel: timerViewModel),
      ),
    );
  }

  setUp(() {
    when(() => mockTimerViewModel.currentTime).thenReturn(Duration.zero);
    when(() => mockTimerViewModel.isRunning).thenReturn(false);
  });

  setUpAll(() {
    registerFallbackValue(ValueNotifier<bool>(false));
  });

  group("TimerWidget", () {
    testWidgets('Exibe tempo inicial zerado TimerWidget', (tester) async {
      await tester.pumpWidget(createWidget(timerViewModel: timerViewModel));

      expect(find.text("00:00"), findsOneWidget);
    });

    testWidgets('Ao iniciar, chama startTimer do TimerViewModel', (
      tester,
    ) async {
      await tester.pumpWidget(createWidget(timerViewModel: mockTimerViewModel));

      final botaoIniciar = find.text("Iniciar");

      expect(botaoIniciar, findsOneWidget);

      await tester.tap(botaoIniciar);
      await tester.pumpAndSettle();
      verify(() => mockTimerViewModel.startTimer(any(), any())).called(1);
    });

    testWidgets("Pausa a contagem ao clicar em Pausar", (tester) async {
      await tester.pumpWidget(createWidget(timerViewModel: timerViewModel));

      final botaoIniciar = find.text("Iniciar");

      await tester.tap(botaoIniciar);
      await tester.pumpAndSettle();
      await tester.pump(Duration(seconds: 10));
      final timerDisplay = find.text("00:10");
      print(timerDisplay);
      expect(timerDisplay, findsOneWidget);
    });
  });
}

Edit:
De fato o provider se fez necessário na hora de testar a TimerPage. A solução então, foi recever na createWidget, por parâmetro tanto TimerType e o TimerViewModel que pode ser o mock ou o TimerViewModel mesmo, dependendo do caso de teste (Mock para testar a chamada dos métodos e o Model para verificar o comprtamento refletido na tela);

solução!

Olá, Gabriel, como vai?

Ótima observação e obrigado por compartilhar os testes e a análise detalhada. Sobre o uso do tester.runAsync, o seu raciocínio está correto. Esse método retorna um Future e, quando não é aguardado com await, o teste pode finalizar antes que as asserções internas sejam realmente avaliadas. Isso explica o comportamento em que o teste passa, mas o console acusa falhas posteriormente. Ao utilizar await tester.runAsync, o fluxo do teste respeita a execução assíncrona, garantindo que as verificações aconteçam no momento correto.

Obrigado por trazer essa sugestão e compartilhar a implementação. Esse tipo de contribuição enriquece muito o fórum e ajuda outros alunos a entenderem melhor as nuances de testes.

Sempre que tiver esse tipo de insight ou dúvida, fique à vontade para postar por aqui. O fórum está à disposição.

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