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?
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();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With