I want to use Provider to inject dependencies into my Flutter app.
I inject a 'repository' class which calls asynchronous native methods.
For testing purposes I want to inject a mock version of the repository.
How does one do this?
In the application code:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Repository()),
],
child: MyApp(),
),
);
}
In the test:
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Repository()),
],
child: MyApp(),
));
Whilst the app runs fine, the test fails with the same message as when I omit the provider and just call
tester.pumpWidget(MyApp());
The Exception is:
The following ProviderNotFoundException was thrown building MyHomePage(dirty, state: _MyHomePageState#9407e): Error: Could not find the correct Provider above this MyHomePage Widget ...
main.dart:
import 'package:flutter/material.dart';
import 'repository.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Repository()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _days = "0";
@override
Widget build(BuildContext buildContext) {
final _daysFuture = context.watch<Repository>().daysInSummer();
return Material(
child: FutureBuilder(
future: _daysFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
_days = snapshot.data;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'demo text',
),
Text(
"$_days",
style: Theme.of(context).textTheme.headline2,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<Repository>().addDayInSummer();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text('Waiting for initialisation'),
SizedBox(
height: 16,
),
CircularProgressIndicator(),
],
);
}
}));
}
}
widget_test.dart:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:summer/main.dart';
class Repository with ChangeNotifier {
int days = 0;
@override
Future<String> addDayInSummer() {
days++;
notifyListeners();
return daysInSummer();
}
@override
Future<String> daysInSummer() {
Future<String> s = Future.delayed(
Duration(milliseconds: 2),
() => days.toString(),
);
notifyListeners();
return s;
}
}
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
await tester.pumpWidget(MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Repository()),
],
child: MyApp(),
));
// Verify that our counter starts at 0.
expect(find.text('0/90'), findsOneWidget);
expect(find.text('1/90'), findsNothing);
//Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0/90'), findsNothing);
expect(find.text('1/90'), findsOneWidget);
});
}