0

I am developing an android / ios application in flutter, and I have chosen to use redux for my state management.

I am writing unit tests for my redux actions, which have been implemented using the async_redux package.

I am following the excellent guidelines set out for testing by the author of the package, but I am not sure how to mock the dispatch of further actions from my action under test.

For example, the below LogoutAction dispatchs a DeleteDatabaseAction and waits for it to complete:

class LogoutAction extends ReduxAction<AppState> {
  @override
  Future<AppState> reduce() async {
    await dispatchFuture(DeleteDatabaseAction());
    return AppState.initialState();
  }
}

class DeleteDatabaseAction extends ReduxAction<AppState> {
  @override
  FutureOr<AppState> reduce() {
    throw StateError(
      'Unwanted call to runtime implementation of DeleteDatabaseAction',
    );
  }
}
void main() {
  final store = Store<AppState>(initialState: AppState(loggedIn: true));
  final storeTester = StoreTester.from(store);

  test('Logout action should return correct state and not throw StateError', () async {
    storeTester.dispatch(LogoutAction());

    TestInfo<AppState> info = await storeTester.wait(LogoutAction);

    expect(info.state.loggedIn, false);
  });
}

I want to test only the action under test, and stub out all further action calls.

i.e. How can I mock / stub the dispatch and dispatchFuture methods on ReduxAction, so that the runtime DeleteDatabaseAction implementation is not run?

So far I have attempted:

  • Inject DeleteDatabaseAction using get_it and inject a mock during test

    • I have 100+ actions that will now need to be added to my context
    • Some actions have parameters that change based on where they are called from, so cannot be registered at app startup
  • Subclass Store, override the above methods and use the subclass in my test here final store = Store<AppState>(initialState: AppState(loggedIn: true))

    • I will not be able to dispatch my action under test, as it uses the same store in the async_redux test implementation
    • Here: storeTester.dispatch(LogoutAction());
  • Create a separate Dispatcher implementation, inject this and override with a mock during tests
    • This will work, but it is new framework, I can go this route but now I am deviating from the well documented framework provided by asyn_redux
Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
Gavin S
  • 579
  • 1
  • 9
  • 21

1 Answers1

1

This wasn't available when you asked this question. But now the answer is here: https://pub.dev/packages/async_redux#mocking-actions-and-reducers

To mock an action and its reducer, start by creating a MockStore in your tests, instead of a regular Store. The MockStore has a mocks parameter which is a map where the keys are action types, and the values are the mocks. For example:

var store = MockStore<AppState>(
  initialState: initialState,  
  mocks: {
     MyAction1 : ...
     MyAction2 : ...
     ...
  },
);

However, there are other ways:

  1. As you said, use get_it or some other dependency injection code to inject the http client into the action reducer. This works well.

  2. Use a DAO. For example:

    class DeleteDatabaseAction extends ReduxAction<AppState> {
       @override
       Future<AppState> reduce() {
          await dao.deleteDatabase();
          return null;
       } 
    

    Then you can mock the DAO itself, or inject the DAO via get_it. You can also make the dao a getter to some BaseAction (that extends ReduxAction) and inject it there.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133