3

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];
}
Jafar ashrafi
  • 511
  • 6
  • 18

2 Answers2

3

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
  // ...
);
Alex Rintt
  • 1,618
  • 1
  • 11
  • 18
3

Although Alex's solution does the job, but it is not the cleanest approach in my opinion, and it does not mention that you apparently had it right in the beginning.

The problem in your code is in the second line CounterCubit counterCubit = CounterCubit();.

Here you instantiate your bloc once and after running the first test it will be closed, thus not available for the rest of the test in the group.

Since the bloc will be closed automatically before evaluating the expectations, you need to re-instantiate the bloc before each test.

The best way to achieve this is using the setUp(() { }); method, instead of instantiating in each test the "build".

Define the bloc as a late variable: then uncomment the code in your setUp(() { }); so it must look like this:

 late CounterCubit counterCubit;

  setUp(() {
         counterCubit = CounterCubit();
        });

You also do not need to close the bloc in the tearDown(() {... }); method, because it happens automatically after each test. (read the quote in the Alex's answer)

moonkin
  • 353
  • 1
  • 10