Bloc: Confusion about the 'expect' argument of 'blocTest'

Created on 20 Jul 2020  ·  2Comments  ·  Source: felangel/bloc

According to README.md

expect is an optional Iterable<State> which the bloc under test is expected to emit after act is executed.

But sometimes actual states also contain states emitted because of the events in build

For example, here is the sample CounterBloc

import 'package:bloc/bloc.dart';

enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.increment:
        final newState = state + 1;
        yield newState;
        break;
      case CounterEvent.decrement:
        final newState = state - 1;
        yield newState;
        break;
    }
  }
}

And here is a test

blocTest(
  "Emits [2] when CounterEvent.increment is added to bloc with state 1",
  build: () async {
    final bloc = CounterBloc();
    bloc.add(CounterEvent.increment);
    return bloc;
  },
  act: (bloc) async {
    bloc.add(CounterEvent.increment);
  },
  expect: [2],
);

The above test behaves as expected and passes

Here is another test

  blocTest(
  "Emits [3] when CounterEvent.increment is added to bloc with state 2",
  build: () async {
    final bloc = CounterBloc();
    bloc.add(CounterEvent.increment);
    bloc.add(CounterEvent.increment);
    return bloc;
  },
  act: (bloc) async {
    bloc.add(CounterEvent.increment);
  },
  expect: [3],
);

This test fails with the following error

Expected: [3]
  Actual: [2, 3]
   Which: was <2> instead of <3> at location [0]
  1. Why did not the second test pass?

  2. If I want to test a bloc's response to an event when added to the bloc in StateX, how should I take my bloc to StateX? Should I do it by passing events in the build(the way I did in the above tests)? Is there a better way?

bloc_test question

Most helpful comment

Thank you for your quick reply 👍. I upgraded to v7.0.0-dev today. Really cool. Now, I feel confident in testing BLoCs.

Waiting for stable release... ❤

All 2 comments

Hi @amanpalariya 👋
Thanks for opening an issue!

The second test fails because the bloc actually emits [2, 3] instead of just [3] which is expected behavior. You can currently modify your bloc to accept an initial state as an argument like:

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc(int initialState) : super(initialState ?? 0);
  ...
}

Then in your blocTest you could do:

blocTest(
  'emits [3] when CounterEvent.increment is added to bloc with state 2',
  build: () async => CounterBloc(2),
  act: (bloc) async => bloc.add(CounterEvent.increment),
  expect: [3],
)

Alternatively, you can leave your bloc implementation as is and use skip to only test the state you want:

blocTest(
  'emits [3] when CounterEvent.increment is added to bloc with state 2',
  build: () async => CounterBloc(),
  act: (bloc) async => bloc..add(CounterEvent.increment)..add(CounterEvent.increment)..add(CounterEvent.increment),
  skip: 3,
  expect: [3],
)

In v7.0.0 (currently out as a dev release v7.0.0-dev.2) you will be able to seed your bloc state like so:

blocTest(
  'emits [3] when increment is added and state is 2',
  build: () => CounterBloc()..emit(2),
  act: (bloc) => bloc.add(CounterEvent.increment),
  expect: [3],
)

I hope that clears things up and I highly recommend upgrading to v7.0.0-dev (stable release coming very soon).

Closing for now but let me know if you have any other questions and I'm happy to continue the conversation 👍

Thank you for your quick reply 👍. I upgraded to v7.0.0-dev today. Really cool. Now, I feel confident in testing BLoCs.

Waiting for stable release... ❤

Was this page helpful?
0 / 5 - 0 ratings