FLUTTER: When testing cubit, loading state isn't emitted, only loaded state is

Viewed 2524

When I run my app normally, I execute cubit.getWeather('London') and the weatherLoading state emits, then weatherLoaded state emits CORRECTLY.

However when I run test for the cubit to test cubit.getWeather('London'), the weatherLoading state isn't emitted - it jumps straight to weatherLoaded state.

Why is this happening?

State:

@freezed
abstract class WeatherState with _$WeatherState {
  factory WeatherState.weatherInitial() = WeatherInitial;
  factory WeatherState.weatherLoading() = WeatherLoading;
  factory WeatherState.weatherLoaded(Weather weather) = WeatherLoaded;
  factory WeatherState.weatherError(String message) = WeatherError;
}

Cubit:

class WeatherCubit extends Cubit<WeatherState> {
  final WeatherRepository _weatherRepository;
  static Weather weather;

  WeatherCubit(this._weatherRepository) : super(WeatherState.weatherInitial());

  Future<void> getWeather(String cityName) async {
    emit(WeatherState.weatherLoading());
    try {
      final weather = await _weatherRepository.fetchWeather(cityName);
      emit(WeatherState.weatherLoaded(weather));
    } on NetworkException {
      emit(WeatherState.weatherError("Network error"));
    }
  }
}

My weather repository class has 'Future<Weather> fetchWeather(String cityname){...}' method.

And finally, my test:

class MockWeatherRepository extends Mock implements WeatherRepository {}

void main() {
  MockWeatherRepository mockWeatherRepository;
  WeatherCubit cubit;

  final weather = Weather(cityName: 'London', temperatureCelsius: 7);

  setUp(() {
    mockWeatherRepository = MockWeatherRepository();
    when(mockWeatherRepository.fetchWeather(any))
        .thenAnswer((_) async => weather);

    cubit = WeatherCubit(mockWeatherRepository);
  });
  test(
    'emits [WeatherLoading, WeatherLoaded] when successful',
    () async {
      cubit.getWeather('London');

      await expectLater(
        cubit,
        emitsInOrder([ //Fails
          WeatherState.weatherLoading(),
          WeatherState.weatherLoaded(weather),
        ]),
      );
    },
  );
}
1 Answers

After a day of pause, I found a solution. Put the expectLater before the cubit "act".
As i read from resoCoder as example of a bloc:

It is usually not be necessary to call expectLater before actually dispatching the event because it takes some time before a Stream emits its first value. I like to err on the safe side though.

I guess this is not true for cubits and you should always call expectLater before calling the method on the cubit.

test(
'emits [WeatherLoading, WeatherLoaded] when successful',
  () {
    expectLater(
      cubit,
      emitsInOrder([ //Fails
        WeatherState.weatherLoading(),
        WeatherState.weatherLoaded(weather),
      ]),
    );
    cubit.getWeather('London');
  },
);

Alternative approach, from reading bloc_test: You can use blocTest:

blocTest creates a new cubit-specific test case with the given description. blocTest will handle asserting that the cubit emits the expected states (in order) after act is executed. blocTest also handles ensuring that no additional states are emitted by closing the cubit stream before evaluating the expectation.

In my case just moving the test to bloctest worked like i expected the test to work.

  blocTest('emits [WeatherLoading, WeatherLoaded] when successful',
  build: () => WeatherCubit(mockWeatherRepository),
  act: (WeatherCubit cubit) {
    when(mockWeatherRepository.fetchWeather(any)).thenAnswer((_) => weather);
    cubit.getWeather('London');
  },
  expect: [
    WeatherState.weatherLoading(),
    WeatherState.weatherLoaded(weather),
  ]);
Related