bizz84
bizz84

Reputation: 2272

Flutter Widget Test: StreamBuilder snapshot has connectionState = waiting with a non-empty Stream

I'm trying to write a widget test for a widget that uses StreamBuilder. In the builder, I return a CircularProgressIndicator if snapshot.hasData is false, otherwise I return a ListView of widgets.

In my test I create a StreamController and add an element to it. When I run the test, I would expect to see snapshot.hasData = true, but instead it's false and I can see that the connectionState is waiting. So my test fails.

Somehow it seems that the first element is not pulled out of the stream, and the connection remains in waiting state. I'm not sure what I'm doing wrong.

Here's my widget test:

testWidgets('Job item pressed - shows edit job page',
  (WidgetTester tester) async {
StreamController<List<Job>> controller =
    StreamController<List<Job>>.broadcast(sync: true);

Job job = Job(id: '0', createdAt: 0, jobName: 'Dart');
controller.add([job]);

final page = JobsPage(
  jobsStream: controller.stream,
);
MockRouter mockRouter = MockRouter();
await tester.pumpWidget(makeTestableWidget(
  child: page,
  auth: MockAuth(),
  database: MockDatabase(),
  router: mockRouter,
));

Finder waiting = find.byType(CircularProgressIndicator);
expect(waiting, findsNothing);

Finder placeholder = find.byType(PlaceholderContent);
expect(placeholder, findsNothing);

Finder item = find.byKey(Key('jobListItem-${job.id}'));
expect(item, findsOneWidget);

await controller.close();
});

And here is my widget code:

Widget _buildContent(BuildContext context) {
return StreamBuilder<List<Job>>(
  stream: jobsStream,
  builder: (context, snapshot) {
    return ListItemsBuilder(
      snapshot: snapshot,
      itemBuilder: (BuildContext context, Job job) {
        return JobListItem(
          key: Key('jobListItem-${job.id}'),
          title: job.jobName, // TODO: This be null?
          onTap: () => _select(context, job),
        );
      },
    );
  },
);
}

And the ListItemsBuilder:

typedef Widget ItemWidgetBuilder<T>(BuildContext context, T item);

class ListItemsBuilder<T> extends StatelessWidget {
  ListItemsBuilder({this.snapshot, this.itemBuilder});
  final AsyncSnapshot<List<T>> snapshot;
  final ItemWidgetBuilder<T> itemBuilder;

  @override
  Widget build(BuildContext context) {
    // prints "waiting"
    print('${snapshot.connectionState.toString()}');
    if (snapshot.hasData) {
      final items = snapshot.data;
      if (items.length > 0) {
        return _buildList(items);
      } else {
        return PlaceholderContent();
      }
    } else if (snapshot.error != null) {
      print('${snapshot.error}');
      return PlaceholderContent(
        title: 'Something went wrong',
        message: 'Can\'t load entries right now',
      );
    } else {
      return Center(child: CircularProgressIndicator());
    }
  }

  Widget _buildList(List<T> items) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return itemBuilder(context, items[index]);
      },
    );
  }
}

I tried using a StreamController.broadcast(sync: true) instead of a simple StreamController, but it didn't make a difference.

Also any additional pump() and pumpAndSettle() calls don't make a difference.

Any ideas?

Upvotes: 5

Views: 6210

Answers (1)

bizz84
bizz84

Reputation: 2272

Solution

Use await tester.pump(Duration.zero);

Full test code:

testWidgets('Job item pressed - shows edit job page',
    (WidgetTester tester) async {
  StreamController<List<Job>> controller = StreamController<List<Job>>();

  Job job = Job(id: '0', createdAt: 0, jobName: 'Dart');
  controller.add([job]);

  final page = JobsPage(
    jobsStream: controller.stream,
  );
  MockRouter mockRouter = MockRouter();
  await tester.pumpWidget(makeTestableWidget(
    child: page,
    auth: MockAuth(),
    database: MockDatabase(),
    router: mockRouter,
  ));

  // this will cause the stream to emit the first event
  await tester.pump(Duration.zero);

  Finder waiting = find.byType(CircularProgressIndicator);
  expect(waiting, findsNothing);

  Finder placeholder = find.byType(PlaceholderContent);
  expect(placeholder, findsNothing);

  Finder item = find.byKey(Key('jobListItem-${job.id}'));
  expect(item, findsOneWidget);

  await controller.close();
});

Upvotes: 13

Related Questions