Search code examples
flutterdartblocflutter-test

Testing for specific states using Flutter bloc_test


I am attempting to test for specific values being emitted using the BloC pattern and bloc_test. Currently, I am able to produce a passing test when I am expecting failure. bloc_test must only check for the Type of states emitted rather than specific values. Is there a way using the bloc_test package to test for specific values of our emitted states?

group('[NewsArticleBloc]', () {
    NewsArticleBloc sut;
    MockNewsRepo mockNewsRepo;
    setUp(() {
      mockNewsRepo = new MockNewsRepo();
      sut = NewsArticleBloc(newsRepo: mockNewsRepo);
    });

    tearDown(() => sut.close());

    blocTest(
      'Next state check',
      build: () => sut,
      act: (bloc) {
        when(mockNewsRepo.getTopHeadlines())
            .thenAnswer((_) => Future.value([Article(author: 'Josh')]));
        bloc.add(GetTopHeadlines());
      },
      expect: [
        TopHeadlines(topHeadlines: [Article(author: 'Micheal')])
                                             //Expecting failure here ^
      ], 
    );
  });
 

Solution

  • If you are happy to use the prerelease version: 8.0.0-nullsafety.0, you can pass an expected state into expect in blocTest, or you can pass a Matcher like in a regular Dart/Flutter test:

    // note expect now requires a function that produces a list
    expect: () => [
      TypeMatcher<TopHeadlines>(),
    ]
    

    This will succeed if the first state emitted has a type: TopHeadlines. If you want to verify some properties of the state in addition to its type, you can also use:

    expect: () => [
      TypeMatcher<TopHeadlines>()
        .having((headlines) => headlines.topHeadlines, 'topHeadlines', hasLength(1)) 
        .having((headlines) => headlines.otherProperty, 'otherProperty', isNotNull)
    ]
    

    If you cannot use prerelease versions in your app, Bloc extends Stream, so you can use regular stream testing methods to verify the correct behaviour:

    final bloc = ...
    final firstStateEmitted = bloc.skip(1).first;  // skip initialState then return the next state
    expect(firstStateEmitted, isA<TopHeadlines>());
    expect(firstStateEmitted.someProperty, isNotNull);