'Widget not rebuilding on notifyListeners during test
I'm trying to build a Widget test for a screen that's using the Provider framework.
The app has 1 screen with 1 button, when I tap the button it will trigger a function to update the state. Once the state is updated a string with the key Key('LoadedString') will appear.
When manually testing in the simulator this works as expected.
When I'm trying to use a widget test to verify the expected behavior it seems like the UI is not updating.
This is the Widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_page_state_controller.dart';
class ProviderPageWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(providers: [
ChangeNotifierProvider<ProviderPageStateController>(
create: (context) => ProviderPageStateController())
], child: HomePage());
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var screenHeight = MediaQuery.of(context).size.height;
ScreenState state =
Provider.of<ProviderPageStateController>(context).pageState;
// Loading state
if (state == ScreenState.Loading) {
return Scaffold(
body: Container(
color: Colors.purple,
child: Center(child: Text('Loading...')),
));
}
// SuccessfullyLoaded state
if (state == ScreenState.SuccessfullyLoaded) {
return Scaffold(
body: Container(
color: Colors.green,
child: Center(
child: Column(
children: [
Padding(
padding:
EdgeInsets.fromLTRB(0, (screenHeight / 2) - 100, 0, 0),
),
Text('Loaded!', key: Key('LoadedString')),
Container(
margin: EdgeInsets.all(25),
color: Colors.blueAccent,
child: TextButton(
child: Text(
'Reset',
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
onPressed: () {
Provider.of<ProviderPageStateController>(context,
listen: false)
.updateState(ScreenState.InitialState);
},
),
),
],
))));
}
// InitialState (=default state)
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(0, (screenHeight / 2) - 100, 0, 0),
),
Container(
key: Key('ButtonA'),
margin: EdgeInsets.all(25),
color: Colors.blueAccent,
child: TextButton(
child: Text(
'Button A',
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
onPressed: () {
Provider.of<ProviderPageStateController>(context,
listen: false)
.functionA();
},
),
),
],
),
),
));
}
}
This is the Controller:
import 'package:flutter/material.dart';
enum ScreenState { InitialState, Loading, SuccessfullyLoaded }
class ProviderPageStateController extends ChangeNotifier {
var pageState = ScreenState.InitialState;
void updateState(ScreenState screenState) {
pageState = screenState;
notifyListeners();
}
Future<void> functionA() async {
updateState(ScreenState.Loading);
Future.delayed(Duration(milliseconds: 1000), () {
updateState(ScreenState.SuccessfullyLoaded);
});
}
Future<void> reset() async {
updateState(ScreenState.InitialState);
}
}
This is the Widget test:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import '../provider_page/provider_page_state_controller.dart';
import '../provider_page/provider_page_widget.dart';
void main() {
testWidgets('ProviderPageWidget SuccessfullyLoaded test',
(WidgetTester tester) async {
final providerPageStateController = ProviderPageStateController();
await tester.pumpWidget(
ListenableProvider<ProviderPageStateController>.value(
value: providerPageStateController,
child: MaterialApp(
home: ProviderPageWidget(),
),
),
);
await providerPageStateController.functionA();
expect(providerPageStateController.pageState, ScreenState.Loading);
await tester.pumpAndSettle(Duration(seconds: 2));
expect(
providerPageStateController.pageState, ScreenState.SuccessfullyLoaded);
// Why does this assert fail?
expect(find.byKey(Key('LoadedString')), findsOneWidget);
});
}
Why is the last expect of my Widget test failing?
Solution 1:[1]
Use the Provider.value constructor, since you are using a variable instead of instantiating an object. And to make sure all dependants are updated eagerly use the the lazy: false flag in tests, even when the value is not accessed. The resulting changes are:
void main() {
final providerPageState = ProviderPageState();
...
Provider<ProviderPageState>.value(
value: providerPageState,
child: MaterialApp(
home: ProviderPageWidget(),
),
lazy: false,
),
);
I would also recommend switching to ChangeNotifierProvider, when using provider to store states. Make sure your ProviderPageState is extending ChangeNotifier and implementing the needed methods, as reference you can use the implementation of ValueNotifier.
Solution 2:[2]
I managed to generate Plots.jl plots by getting from IJulia the same configuration it uses to run notebooks (this is probably the most sure way when you have many Pyhtons etc.).
using Conda, IJulia
Conda.add("nbconvert") # I made sure nbconvert is installed
mycmd = IJulia.find_jupyter_subcommand("nbconvert")
append!(mycmd.exec, ["--ExecutePreprocessor.timeout=600","--to", "notebook" ,"--execute", "note1.ipynb"])
Now mycmd has exactly the same environment as seen by IJulia so we can do run(mycmd):
julia> run(mycmd)
[NbConvertApp] Converting notebook note1.ipynb to notebook
Starting kernel event loops.
[NbConvertApp] Writing 23722 bytes to note1.nbconvert.ipynb
The outcome got saved to note1.nbconvert.ipynb, I open it with nteract to show that graphs actually got generated:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | hitshy_dev |
| Solution 2 | Przemyslaw Szufel |

