Flutter测试报错:No GoRouter found in context,如何Mock上下文?
解决GoRouter命名路由跳转的ViewModel单元测试问题
你在测试调用context.goNamed的ViewModel时,因为Mock的BuildContext中没有GoRouter实例,导致报错"No GoRouter found in context"。这是因为goNamed是GoRouter的扩展方法,并非BuildContext本身的方法,直接Mock BuildContext的goNamed无效。以下是两种可行的解决方案:
方案一:Mock GoRouter并关联到上下文
无需修改现有ViewModel代码,通过Mock GoRouter并让GoRouter.of(context)返回这个Mock实例来解决:
- 创建MockGoRouter类:
import 'package:go_router/go_router.dart'; import 'package:mockito/mockito.dart'; class MockGoRouter extends Mock implements GoRouter {}
- 修改测试代码:
void main() { group("MainScreenViewModel Tests", () { late MainScreenViewModel viewModel; late MockGoRouter mockRouter; setUp(() { viewModel = MainScreenViewModel(); mockRouter = MockGoRouter(); // 让GoRouter.of(context)返回Mock实例 when(() => GoRouter.of(any())).thenReturn(mockRouter); }); test("MoveToTab should change currentIndex and trigger navigation", () { final context = MockBuildContext(); int notifyCount = 0; // 监听notifyListeners调用次数 viewModel.addListener(() => notifyCount++); viewModel.moveToTab(context, index: 1); expect(viewModel.currentIndex, 1); // 验证路由跳转方法被正确调用 verify(() => mockRouter.goNamed("/b")).called(1); // 验证notifyListeners被触发 expect(notifyCount, 1); }); }); }
方案二:重构ViewModel,依赖注入GoRouter(推荐)
将GoRouter从上下文抽离,通过构造函数注入到ViewModel中,解耦ViewModel与BuildContext,测试逻辑更简洁:
- 修改ViewModel代码:
import 'package:go_router/go_router.dart'; import 'package:flutter/foundation.dart'; const List<String> routeList = ["/a", "/b"]; class MainScreenViewModel extends ChangeNotifier { final GoRouter _router; int _currentIndex = 0; int get currentIndex => _currentIndex; // 构造函数注入GoRouter MainScreenViewModel(this._router); void moveToTab({required int index}) { _currentIndex = index; _router.goNamed(routeList[index]); notifyListeners(); } }
- 对应的测试代码:
import 'package:mockito/mockito.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group("MainScreenViewModel Tests", () { late MainScreenViewModel viewModel; late MockGoRouter mockRouter; setUp(() { mockRouter = MockGoRouter(); viewModel = MainScreenViewModel(mockRouter); }); test("MoveToTab should change currentIndex and trigger navigation", () { int notifyCount = 0; viewModel.addListener(() => notifyCount++); viewModel.moveToTab(index: 1); expect(viewModel.currentIndex, 1); verify(() => mockRouter.goNamed("/b")).called(1); expect(notifyCount, 1); }); }); } class MockGoRouter extends Mock implements GoRouter {}
方案对比
- 方案一:无需修改现有业务代码,适合快速修复测试问题,但仍保留了ViewModel对BuildContext的依赖。
- 方案二:通过依赖注入解耦,符合单一职责原则,测试逻辑更清晰,是更优的架构选择。
内容的提问来源于stack exchange,提问作者maxmitz
相关产品推荐
相关产品推荐

