小部件在测试期间不在 notifyListeners 上重建
Widget not rebuilding on notifyListeners during test
我正在尝试为使用 Provider 框架的屏幕构建 Widget 测试。
该应用程序有 1 个屏幕和 1 个按钮,当我点击按钮时,它会触发更新状态的功能。更新状态后,将出现一个带有键 Key('LoadedString')
的字符串。
在模拟器中手动测试时,这按预期工作。
当我尝试使用小部件测试来验证预期行为时,UI 似乎没有更新。
这是小部件:
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();
},
),
),
],
),
),
));
}
}
这是控制器:
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);
}
}
这是小部件测试:
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);
});
}
为什么我的 Widget 测试的最后一个 expect
失败了?
使用 Provider.value
构造函数,因为您使用的是变量而不是实例化对象。为了确保所有依赖项都得到更新,请在测试中使用 lazy: false
标志,即使值为 not accessed。产生的变化是:
void main() {
final providerPageState = ProviderPageState();
...
Provider<ProviderPageState>.value(
value: providerPageState,
child: MaterialApp(
home: ProviderPageWidget(),
),
lazy: false,
),
);
我还建议切换到 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
。
我发现了问题所在。我没有在小部件测试中抽取正确的小部件。
将小部件测试更新为以下解决了问题:
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: HomePage(),
),
),
);
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);
});
}
变化已上线:
home: HomePage(),
我正在尝试为使用 Provider 框架的屏幕构建 Widget 测试。
该应用程序有 1 个屏幕和 1 个按钮,当我点击按钮时,它会触发更新状态的功能。更新状态后,将出现一个带有键 Key('LoadedString')
的字符串。
在模拟器中手动测试时,这按预期工作。
当我尝试使用小部件测试来验证预期行为时,UI 似乎没有更新。
这是小部件:
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();
},
),
),
],
),
),
));
}
}
这是控制器:
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);
}
}
这是小部件测试:
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);
});
}
为什么我的 Widget 测试的最后一个 expect
失败了?
使用 Provider.value
构造函数,因为您使用的是变量而不是实例化对象。为了确保所有依赖项都得到更新,请在测试中使用 lazy: false
标志,即使值为 not accessed。产生的变化是:
void main() {
final providerPageState = ProviderPageState();
...
Provider<ProviderPageState>.value(
value: providerPageState,
child: MaterialApp(
home: ProviderPageWidget(),
),
lazy: false,
),
);
我还建议切换到 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
。
我发现了问题所在。我没有在小部件测试中抽取正确的小部件。
将小部件测试更新为以下解决了问题:
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: HomePage(),
),
),
);
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);
});
}
变化已上线:
home: HomePage(),