You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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实例来解决:

  1. 创建MockGoRouter类:
import 'package:go_router/go_router.dart';
import 'package:mockito/mockito.dart';

class MockGoRouter extends Mock implements GoRouter {}
  1. 修改测试代码:
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,测试逻辑更简洁:

  1. 修改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();
  }
}
  1. 对应的测试代码:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 02:10:15