比方说,我使用WidgetTester
对Flutter中的屏幕进行了测试。有一个按钮,通过Navigator
执行导航。我想测试该按钮的行为。
空间/屏幕
class MyScreen extends StatefulWidget {
MyScreen({Key key}) : super(key: key);
@override
_MyScreenState createState() => _MyScreenScreenState();
}
class _MyScreenState extends State<MyScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed("/nextscreen");
},
child: Text(Strings.traktTvUrl)
)
)
);
}
}
测试
void main() {
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: MyScreen()));
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
//how to test navigator?
});
}
我有一个正确的方法来检查导航器是否被调用?或者有没有办法模拟和替换导航器?
请注意,上面的代码实际上会因异常而失败,因为在应用程序中没有声明的命名路由'/nextscreen'
。这很容易解决,你不需要指出它。
我主要关注的是如何在Flutter中正确处理这个测试场景。
答案 0 :(得分:15)
虽然Danny所说的是正确的并且可行,但是您也可以创建一个模拟的NavigatorObserver以避免任何额外的重复:
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
这将转换为您的测试用例,如下所示:
void main() {
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
final mockObserver = MockNavigatorObserver();
await tester.pumpWidget(
MaterialApp(
home: MyScreen(),
navigatorObservers: [mockObserver],
),
);
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
await tester.pumpAndSettle();
/// Verify that a push event happened
verify(mockObserver.didPush(typed(any), typed(any)));
/// You'd also want to be sure that your page is now
/// present in the screen.
expect(find.byType(DetailsPage), findsOneWidget);
});
}
我在自己的博客which you can find here上写了一篇有关此问题的深入文章。
答案 1 :(得分:4)
在navigator tests in the flutter repo中,他们使用NavigatorObserver类来观察导航:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<button class="button">Submit</button>
</form>
这看起来应该可以做你想要的,但它可能只能在顶级(MaterialApp)工作,我不确定你是否可以只将它提供给一个小部件。
答案 2 :(得分:1)
以下解决方案是,一般的方法,并不是特定于Flutter。
导航可以从屏幕或小部件中抽象出来。测试可以模拟并注入此抽象。这种方法应该足以测试这种行为。
有几种方法可以实现这一目标。为了回应这个问题,我将展示其中一个。也许有可能简化它或使它更“Darty”。
导航抽象
class AppNavigatorFactory {
AppNavigator get(BuildContext context) =>
AppNavigator._forNavigator(Navigator.of(context));
}
class TestAppNavigatorFactory extends AppNavigatorFactory {
final AppNavigator mockAppNavigator;
TestAppNavigatorFactory(this.mockAppNavigator);
@override
AppNavigator get(BuildContext context) => mockAppNavigator;
}
class AppNavigator {
NavigatorState _flutterNavigator;
AppNavigator._forNavigator(this._flutterNavigator);
void showNextscreen() {
_flutterNavigator.pushNamed('/nextscreen');
}
}
注入小部件
class MyScreen extends StatefulWidget {
final _appNavigatorFactory;
MyScreen(this._appNavigatorFactory, {Key key}) : super(key: key);
@override
_MyScreenState createState() => _MyScreenScreenState(_appNavigatorFactory);
}
class _MyScreenState extends State<MyScreen> {
final _appNavigatorFactory;
_MyScreenState(this._appNavigatorFactory);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
_appNavigatorFactory.get(context).showNextscreen();
},
child: Text(Strings.traktTvUrl)
)
)
);
}
}
测试示例(使用Mockito for Dart)
class MockAppNavigator extends Mock implements AppNavigator {}
void main() {
final appNavigator = MockAppNavigator();
setUp(() {
reset(appNavigator);
});
testWidgets('Button is present and triggers navigation after tapped',
(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: MyScreen(TestAppNavigatorFactory())));
expect(find.byType(RaisedButton), findsOneWidget);
await tester.tap(find.byType(RaisedButton));
verify(appNavigator.showNextscreen());
});
}
答案 3 :(得分:0)
为什么不直接使用 npx react-native init HelloRN
?
Navigator
在你的测试中,它应该是这样的:
Widget bindWithNavigator(Widget widget) {
// Navigator dependency
return MaterialApp(
home: Navigator(
onPopPage: (route, result) => route.didPop(result),
pages: [
MaterialPage(child: Container()),
MaterialPage(child: widget),
],
),
);
}
在void main() {
testWidgets('should pop', (WidgetTester tester) async {
await tester.pumpWidget(bindWithNavigator(MyWidget()));
await tester.tap(find.byIcon(Icons.back));
await tester.pumpAndSettle();
expect(find.byIcon(Icons.back), findsNothing);
});
}
中应该是一样的。