marcglasberg / async_redux

Flutter Package: A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.
Other
230 stars 41 forks source link

How to test "navigateAction" in widget test? #109

Closed ibelz closed 3 years ago

ibelz commented 3 years ago

Hi, I want to write a widget test with a button tap which dispatches 'NavigateAction.pushReplacementNamed'.

I have already tried to mock the action "NavigateAction", but the Mock will not be fired. The mock of "NavigateAction.pushReplacementNamed" is not allowed (Error: The getter 'pushReplacementNamed' isn't defined for the type 'NavigateAction'.).

I have already read this in documentation too, but I have no clue from where to get the variable "actions".

var navigateAction = actions.get(NavigateAction).action as NavigateAction; expect(navigateAction.type, NavigateType.pushNamed); expect((navigateAction.details as NavigatorDetails_PushNamed).routeName, "myRoute");`

That's my test at the moment:

    testWidgets('navigates to appScreen after offline tap', (tester) async {
      await tester.pumpWidget(
        BaseTestWidget(
          store: MockStore<AppState>(initialState: initState, mocks: {
            LoginAction: LoginMockAction(),
            NavigateAction: (details) => NavigateMockAction(details),
          }),
          child: LoginScreenConnector(),
        ),
      );

      expectLater(
        () => tester.tap(find.text('auth_login_btn_offline')),
        prints('xxx\n'),
      );
    });
  });

  class NavigateMockAction extends MockAction<AppState> {
  NavigatorDetails details;

  NavigateMockAction(this.details);

  Future<AppState?> reduce() async {
    print('xxx');
    print(details);
    return null;
  }
}
marcglasberg commented 3 years ago

There is no need to mock the NavigateAction.

Use the StoreTester to collect a TestInfoList, and from its TestInfos you can get all the actions that were dispatched.

Then you can expect that the appropriate actions are of type NavigateAction, and that the (action as NavigateAction).type is NavigateType.pushReplacementNamed.

ibelz commented 3 years ago

Hey Marc, thanks for your quick answer. I use the StoreTester already in my normal unit tests - and there it works really fine, but I have no clue how to handle the StoreTester together with a widget test (like in my code sample).

I want to test, that the tap of the button on the widget fires the wired up navigate action. I cannot use the StoreTester as store in my BaseTestWidget - or have I overseen something?

Is there somewhere a complete example for such a widget test or please can you fix my code above?

Thanks

marcglasberg commented 3 years ago

Usually you have 3 types of tests: The business tests, where you'd test the NavigateAction with the StoreTester. Then the presentation tests, where you'd test that the dumb widget fires the appropriate callback. And then the connector test would simply test the wiring between the dumb widget and the store.

It seems to me you are taking a different approach, which is to run everything together and do an integrated test. I see two ways of doing it:

1) When you click the widget, let it navigate to the new screen and then test that the new screen is present in the widget tree.

or

2) You can use the Store's ActionObserver to record all actions:

class MyActionObserver implements ActionObserver<AppState> {
  Map<Type, Action> actions;
  void observe(ReduxAction<St> action, int dispatchCount, {@required bool ini}) => actions[action.runtimeType] = action;
}

And then:

var navigateAction = observer.actions.get(NavigateAction).action as NavigateAction;
expect(navigateAction.type, NavigateType.pushNamed);
expect((navigateAction.details as NavigatorDetails_PushNamed).routeName, "myRoute");`
ibelz commented 3 years ago

Hey Marc, thank you very much for your comment.

You are right, it is a kind of small integration test when I test the widget and connector together. ;)

I have added your second proposal (with some little fixes) and it works like a charm. I didn't even think about using the action observer in the test. That's a great idea! 👍

Thank you very much for your work. I love "async redux" :)