Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

flutter

dart

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?

like image 828
bizz84 Avatar asked Aug 10 '18 12:08

bizz84


1 Answers

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();
});
like image 55
bizz84 Avatar answered Oct 17 '22 06:10

bizz84