Search code examples
flutterdartbloccubitbloc-test

bloc_test: Bad state: Cannot emit new states after calling close


I have a problem in Test section of flutter, I wrote a test for simple counter app which uses cubit, but while I run all test it gives me the error mentioned in header, any body knows why?

it is necessary to say that while I run the tests one by one all runs successfully, but when I run all group it returns that error in second and third test...

these are my code:

group("Counter Cubit", () {
    CounterCubit counterCubit = CounterCubit();
    setUp(() {
      //counterCubit = CounterCubit();
    });

    tearDown(() {
      counterCubit.close();
    });

    test("The initial state for counterCubit is CounterState(counterValue: 0)",
        () {
      expect(counterCubit.state, CounterState(0, false));
    });

    blocTest(
        "The cubit should emit CounterState(counter: 1, isIncrement: true) while we call 
          counterCubit.increment() ",
        build: () => counterCubit,
        act: (CounterCubit cubit) => cubit.increment(),
        expect: () => [CounterState(1, true)]);

    blocTest(
        "The cubit should emit CounterState(counter: -1, isIncrement: false) while we call 
           counterCubit.decrement() ",
        build: () => counterCubit,
        act: (CounterCubit cubit) => cubit.decrement(),
        expect: () => [CounterState(-1, false)]);
});

and my cubit and state are like below:

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterState(0, false));

  void increment() => emit(CounterState(state.counter + 1, true));

  void decrement() => emit(CounterState(state.counter - 1, false));
}

and state like:

class CounterState extends Equatable {
  final int counter;
  final bool isIncremented;
  CounterState(this.counter, this.isIncremented);

  @override
  List<Object?> get props => [counter, isIncremented];
}

Solution

  • bloc_test documentation says:

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

    So basically when you run:

    blocTest(
      // ...
      build: () => counterCubit, // Same instance for all tests
      // ...
    );
    

    It disposes your BLoC instance (that you pass through build parameter). So since the second test is using the same instance of the first test it throws an exception because in truth it was closed by the last blocTest call (in the previous test).

    And it also answer why running tests one by one works but not the group.

    To fix pass a new instance when running blocTest (through the same parameter):

    blocTest(
      // ...
      build: () => CounterCubit(), // Create a new instance
      // ...
    );