VeryGoodOpenSource / mockingjay

A package that makes it easy to mock, test, and verify navigation in Flutter. Created by Very Good Ventures 🦄
https://pub.dev/packages/mockingjay
MIT License
112 stars 7 forks source link

Navigator.pop is never called while it actually was #67

Open egonm12 opened 7 months ago

egonm12 commented 7 months ago

Describe the bug When I open a dialog with showDialog and I pop it, verify tells me it never called .pop while it did.

popping...
popped!
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
No matching calls. All calls: [VERIFIED] MockNavigator.canPop()
(If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)

When the exception was thrown, this was the stack:
#0      fail (package:matcher/src/expect/expect.dart:149:31)
#1      _VerifyCall._checkWith (package:mocktail/src/mocktail.dart:728:7)
#2      _makeVerify.<anonymous closure> (package:mocktail/src/mocktail.dart:519:18)
#3      main.<anonymous closure>.<anonymous closure> (file:///Users/path/to/file/flutter_mockingjay/test/main_test.dart:33:13)
<asynchronous suspension>
#4      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:168:15)
<asynchronous suspension>
#5      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1013:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

The test description was:
  can pop a dialog
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: can pop a dialog

✖ MainApp can pop a dialog
popping...
popped!
✓ MainApp can pop a normal widget

To Reproduce

Reproducible example: https://github.com/egonm12/flutter_mockingjay/tree/main

void main() {
  group(MainApp, () {
    late MockNavigator mockNavigator;

    setUp(() {
      mockNavigator = MockNavigator();

      when(() => mockNavigator.canPop()).thenReturn(true);
      when(() => mockNavigator.pop(any())).thenReturn(null);
    });

    testWidgets('can pop a dialog', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: MockNavigatorProvider(
            navigator: mockNavigator,
            child: const MyFloatingActionButton(),
          ),
        ),
      );
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pump();
      await tester.pumpAndSettle();

      await tester.tap(find.byType(TextButton));

      verify(() => mockNavigator.canPop()).called(1);
      verify(() => mockNavigator.pop<Object?>(any())).called(1);
    });

    testWidgets('can pop a normal widget', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: MockNavigatorProvider(
            navigator: mockNavigator,
            child: const MyWidget(),
          ),
        ),
      );
      await tester.tap(find.byType(ElevatedButton));

      verify(() => mockNavigator.canPop()).called(1);
      verify(() => mockNavigator.pop<Object?>(any())).called(1);
    });
  });
}

Expected behavior

All tests pass

robsonsilv4 commented 1 month ago

This happens because showDialog is looking up for the root Navigator created by the MaterialApp.

I think this test scenario it's not a test case for a mocked navigation, where you are navigating between two routes. You can cover these scenarios best with integration tests or using a concrete Navigator.

But if you really want to try, provide a Navigator above MaterialApp and replace it in tests, or, receive a Navigator as parameter in the MyFloatingActionButton, and pass the mock during tests.

alestiago commented 3 weeks ago

Thanks @robsonsilv4 for taking the time to reply, @egonm12 did the reply help solve your issue?