brianegan / redux_thunk

Redux Middleware for handling functions as actions
MIT License
88 stars 8 forks source link

Testing with Dart Redux Thunks #15

Open aprofromindia opened 5 years ago

aprofromindia commented 5 years ago

I have written the following code and am trying to test a redux thunk action in flutter. Unfortunately when I debug the code, I can see the thunkMiddleware doesn't intercept the action as expected (as the returned Function(store) never gets triggered).

state.dart

@JsonSerialize()
class Pokemon {
  final String name;
  final String url;

  Pokemon(this.name, this.url);

  factory Pokemon.fromJson(Map<String, dynamic> json) =>
      _$PokemonFromJson(json);
}

// state
@immutable
class PokemonsState {
  final List<Pokemon> pokemons;
  final bool isLoading;
  final Exception ex;

  const PokemonsState(
      {this.pokemons = const [], this.isLoading = false, this.ex});

  PokemonsState copyWith(
      {List<Pokemon> pokemons, bool isLoading, Exception ex}) {
    return PokemonsState(
        pokemons: pokemons ?? this.pokemons,
        isLoading: isLoading ?? this.isLoading,
        ex: ex ?? this.ex);
  }
}

// reducer
PokemonsState pokemonsState(PokemonsState state, action) {
  switch (action.runtimeType) {
    case FetchPokemons:
      return state.copyWith(isLoading: true);
    case AddPokemons:
      if (action.error == null)
        return state.copyWith(
            pokemons: action.payload, isLoading: false, ex: null);

      return state.copyWith(ex: action.error, isLoading: false);
  }
  return state;
}

// actions
class FetchPokemons {}

class AddPokemons {
  final List<Pokemon> payload;
  final Exception error;

  AddPokemons({this.payload, this.error});
}

// thunks
loadPokemons(Client client) {
  return (Store<AppState> store) async {
    store.dispatch(FetchPokemons());
    try {
      var res = await client.get(pokemonUrl);
      if (res.statusCode == HttpStatus.ok) {
        final pokemons = jsonDecode(res.body)['results'];
        store.dispatch(AddPokemons(
            payload:
                List<Pokemon>.from(pokemons.map((i) => Pokemon.fromJson(i)))));
      } else {
        throw HttpException(res.reasonPhrase);
      }
    } on Exception catch (e) {
      store.dispatch(AddPokemons(error: e));
    }
  };
}

state_test.dart

class MockClient extends Mock implements Client{}

void main(){

    Store<PokemonsState> store;

    setUp(() {
      store = Store(pokemonsState,
          initialState: PokemonsState(), middleware: [thunkMiddleware]);
    });

    test('add Pokemons success should add a list of pokemons to the store',
            () {
          final client = MockClient();

          when(client.get(argThat(isInstanceOf<String>()))).thenAnswer((_) async =>
              Response(
                  '{"results": [{"name": "p1", "url": "u1"}, {"name": "p2", "url": "u2"}]}',
                  200));

          store.dispatch(loadPokemons(client));
          expect(store.state.pokemons.length, 2);
        });
      });

}

Any help in fixing the test would be most appreciated!

AndiDog commented 3 years ago

Thunk actions are asynchronous and therefore you won't immediately see the expected result in the state, but with a small delay. You might try a CallableThunkAction and then await MyCallableThunkAction().call(store) instead of store.dispatch(...), but I haven't tried that. In case the thunk action dispatches more asynchronous actions, and you need to wait for them to be finished, that suggestion will surely not work.