How to test navigation via Navigator in Flutter

In the navigator tests in the flutter repo they use the NavigatorObserver class to observe navigations:

class TestObserver extends NavigatorObserver {
  OnObservation onPushed;
  OnObservation onPopped;
  OnObservation onRemoved;
  OnObservation onReplaced;

  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (onPushed != null) {
      onPushed(route, previousRoute);
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (onPopped != null) {
      onPopped(route, previousRoute);
    }
  }

  @override
  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (onRemoved != null)
      onRemoved(route, previousRoute);
  }

  @override
  void didReplace({ Route<dynamic> oldRoute, Route<dynamic> newRoute }) {
    if (onReplaced != null)
      onReplaced(newRoute, oldRoute);
  }
}

This looks like it should do what you want, however it may only work form the top level (MaterialApp), I'm not sure if you can provide it to just a widget.


This is modified version of the other answer to show how to do it with mocktail instead of mockito:

import 'package:mocktail/mocktail.dart';

class MockNavigatorObserver extends Mock implements NavigatorObserver {}

class FakeRoute extends Fake implements Route {}
void main() {
  setUpAll(() {
    registerFallbackValue(FakeRoute());
  });

  testWidgets('Button is present and triggers navigation after tapped',
      (WidgetTester tester) async {
    final mockObserver = MockNavigatorObserver();
    await tester.pumpWidget(
      MaterialApp(
        home: MyScreen(),
        navigatorObservers: [mockObserver],
      ),
    );

    expect(find.byType(RaisedButton), findsOneWidget);
    await tester.tap(find.byType(RaisedButton));
    await tester.pumpAndSettle();

    verify(mockObserver.didPush(any(), any()));

    expect(find.byType(DetailsPage), findsOneWidget);
  });
}

While what Danny said is correct and works, you can also create a mocked NavigatorObserver to avoid any extra boilerplate:

import 'package:mockito/mockito.dart';

class MockNavigatorObserver extends Mock implements NavigatorObserver {}

That would translate to your test case as follows:

void main() {
  testWidgets('Button is present and triggers navigation after tapped',
      (WidgetTester tester) async {
    final mockObserver = MockNavigatorObserver();
    await tester.pumpWidget(
      MaterialApp(
        home: MyScreen(),
        navigatorObservers: [mockObserver],
      ),
    );

    expect(find.byType(RaisedButton), findsOneWidget);
    await tester.tap(find.byType(RaisedButton));
    await tester.pumpAndSettle();

    /// Verify that a push event happened
    verify(mockObserver.didPush(any, any));

    /// You'd also want to be sure that your page is now
    /// present in the screen.
    expect(find.byType(DetailsPage), findsOneWidget);
  });
}

I wrote an in-depth article about this on my blog, which you can find here.