A bloc extension to test async operations using bloc_test.

The problem

bloc_test provides a wait parameter to delay the execution of the expect block to test async operations.

When the async operation takes longer than the wait duration, the test fails as not all states defined in expected have been emitted on time.

Thus the wait parameter has to be set to a static, estimated duration. This is problematic as any changes affecting the actual duration might need an adjustment of the wait duration.

The solution

Use the bloc_test_async package to ditch the wait parameter and instead await the completion of events.

  blocTest("ExampleBloc",
      build: () => ExampleBloc(),
      act: (bloc) async {
        await bloc.addToComplete(ExampleEvent());
      },
      expect: () => [ExampleLoadingState(), ExampleSuccessState()]);

The extension method addToComplete adds an event to a bloc and returns a Future to await the completion of the event in the bloc. The wait parameter is left out as it is ensured that the events added in act have been completed.

Migrating

Add the dependency to your pubspec.yaml:

dependencies:
  bloc_test_async: ^0.1.0

Bloc

Your bloc implementation has to be adjusted to complete events to use addToComplete in your test. Simply adjust your calls from on to completeOn:

class MyBloc extends Bloc<Event, State> {
    MyBloc() : super(Initial()) {
        //previously on<Event>((event, emit)
       completeOn<Event>((event, emit) async {
            emit(Loading());
            // do stuff
            emit(Success());
        });
    }
}

When completeOn is run outside of a test context, the code to signal event completion is skipped and it behaves just like on.

blocTest

In your blocTest remove the wait parameter if necessary and use await addToComplete() instead of add() in the act block. The act block must be defined as async to use await.

  blocTest("MyBloc",
      build: () => MyBloc(),
      act: (bloc) async {
        //previously bloc.add(Event());
        await bloc.addToComplete(Event());
      },
      expect: () => [Loading(), Success()]);

When addToComplete is used outside of a test context, an exception is thrown.

Timeouts

the addToComplete method provides a timeout parameter to define a timeout for the completion of an event.

  blocTest("MyBloc",
      build: () => MyBloc(),
      act: (bloc) async {
        await bloc.addToComplete(Event(), timeout: Duration(seconds: 3));
      },
      expect: () => [Loading(), Success()]);

The default is set to 5 seconds.

If the timeout occurs, a TimeoutException is thrown and the test fails.

Libraries

bloc_test_async