Flutter 集成测试策略:Mock 测试 vs 真实后端测试

创建日期: 2026-02-06  |  适用范围: Employee App、Guest App、Kiosk App

核心理念:每个 Flutter App 都应该同时拥有两套集成测试 — Mock 测试验证"UI 本身没坏",真实后端测试验证"前后端能跑通"。两者互补,缺一不可。

1. 对比总览

维度 Mock 测试 真实后端测试
后端依赖 ❌ 不需要后端运行 ✅ 需要 localhost:3000 运行
数据库依赖 ❌ 不需要数据库 ✅ 需要 PostgreSQL + 种子数据
执行速度 ⚡ 快(秒级) 🐢 慢(需要网络 I/O + DB 查询)
CI 友好度 ⭐⭐⭐ 无外部依赖,随时可跑 ⭐⭐ 需要 Docker/后端服务
验证范围 UI 渲染 + 交互逻辑 + 状态管理 端到端数据流 + API 契约 + 认证
边界场景 ⭐⭐⭐ 精确模拟任意场景(500、超时、空数据) ⭐ 只能测试实际可达的场景
API 契约验证 ❌ mock 数据可能与真实 API 不一致 ✅ 真实验证前后端数据格式匹配
数据一致性 ❌ mock 数据是静态的,不反映 DB 变更 ✅ 使用真实数据库,反映最新 schema
稳定性 ⭐⭐⭐ 确定性高,几乎不 flaky ⭐⭐ 可能因网络/数据/时序问题 flaky
维护成本 需要手动维护 mock 数据(API 变更时同步更新) 需要维护种子数据 + 后端环境

2. Mock 测试详解

2.1 工作原理

Flutter App Dio HTTP Client MockInterceptor(拦截) 返回预定义 JSON

核心是在 Dio 的 Interceptor 层拦截所有 HTTP 请求,根据路径和方法返回预定义的 JSON 响应,完全不触及真实网络。

2.2 技术实现

// MockInterceptor — 路由注册模式
class MockInterceptor extends Interceptor {
  final Map<String, dynamic Function(RequestOptions)> _routes = {};

  void register(String method, String path, dynamic Function(RequestOptions) handler) {
    _routes['$method:$path'] = handler;
  }

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final key = '${options.method}:${options.path}';
    final route = _routes[key];
    if (route != null) {
      handler.resolve(Response(
        requestOptions: options,
        statusCode: 200,
        data: route(options),
      ));
    } else {
      handler.resolve(Response(
        requestOptions: options,
        statusCode: 404,
        data: {'error': 'Mock route not found: $key'},
      ));
    }
  }
}

2.3 适合测试的场景

场景示例为什么 Mock 更适合
空数据 门店列表为空、预约列表为空 真实后端很难保证空数据状态
服务器错误 500 Internal Server Error 不可能让真实后端故意返回 500
网络超时 请求超过 10 秒无响应 真实网络超时不可控
数据异常 API 返回意外格式的 JSON 真实后端不会返回错误格式
Loading 状态 验证加载动画正确显示 可精确控制响应延迟
权限不足 403 Forbidden 的 UI 处理 可精确模拟权限拒绝
Token 过期 401 后的重新登录流程 可精确控制认证状态
Mock 测试的风险:如果后端 API 格式发生变更(字段重命名、新增必填字段、响应结构调整),mock 数据不会自动更新,测试可能继续通过但实际已经不兼容。必须在 API 变更时同步更新 mock 数据。

3. 真实后端测试详解

3.1 工作原理

Flutter App Dio HTTP Client localhost:3000(Express.js) PostgreSQL

Flutter App 直接连接运行中的后端服务,走完整的 HTTP → 路由 → 中间件 → Service → DB 链路。

3.2 前置条件

# 1. 确保后端运行
npm run start:backend

# 2. 确保种子数据已加载
npm run db:seed:dev

# 3. 运行测试
cd employee_mobile_app/qqnails_employee_app
flutter test integration_test/

3.3 适合测试的场景

场景示例为什么真实后端更适合
登录认证 输入账号密码 → 获取 JWT → 访问受保护资源 验证真实认证链路
数据 CRUD 创建预约 → 查看列表 → 确认存在 验证数据真正被写入数据库
API 契约 后端返回的 JSON 结构 App 能正确解析 捕获字段重命名/类型变更
权限验证 员工角色不能访问管理员接口 验证后端 RBAC 中间件
业务流程 完整预约流程:选服务 → 选技师 → 选时间 → 确认 验证整个流程无断点
数据关联 签到后预约状态从 confirmed → checked_in 验证多表联动
真实测试的风险 应对方式:使用专用测试账号 + 每次测试前重置关键状态 + 合理的超时设置。

4. 两者如何互补

理想的测试金字塔

Bug 类型Mock 能发现?真实测试能发现?
按钮点击后 UI 没更新
空列表时页面崩溃 ❌(真实数据不为空)
后端返回字段从 store_name 改为 storeName ❌(mock 不会自动更新)
网络断开时 App 崩溃 ❌(测试环境网络正常)
JWT 过期后 refresh token 不工作 ❌(mock 跳过认证)
预约时间冲突检查失效 ❌(mock 不校验业务规则)
Loading 动画卡住不消失 ✅(可控制响应时机) ❌(可能太快看不到)

5. 目录结构规范

统一的三个 App 测试目录结构:

{app}/integration_test/
├── mock/                        # Mock 测试(不连接后端)
│   ├── mock_interceptor.dart    # Dio 拦截器,按路由返回 mock 数据
│   ├── fake_secure_storage.dart # 内存 SecureStorage
│   ├── t01_xxx_mock_test.dart   # 各 mock 测试文件
│   └── ...
├── helpers/                     # 真实后端测试辅助
│   ├── {app}_test_config.dart   # extends BaseTestConfig
│   ├── {app}_test_helpers.dart  # App 特有的 helper 方法
│   └── {app}_test_actions.dart  # 高级业务操作
├── t01_xxx_test.dart            # 真实后端测试文件
├── t02_xxx_test.dart
└── ...

运行命令

# 只跑 mock 测试(不需要后端)
flutter test integration_test/mock/

# 只跑真实后端测试(需要后端运行)
flutter test integration_test/t01_*.dart integration_test/t02_*.dart ...

# 跑全部
flutter test integration_test/

6. 当前三个 App 的状态与计划

AppMock 测试真实后端测试计划
Employee App ❌ 无 ✅ 20 个测试 补 mock 测试(~5个,覆盖边界场景)
Guest App ❌ 无 ❌ 无(仅 widget 测试) 两者都要新建
Kiosk App ✅ 12 个测试 ❌ 无 补真实后端测试(~6个,覆盖核心流程)

7. 共享测试基础设施

为避免三个 App 各写一套相同的工具代码,提取公共部分到 packages/flutter_test_shared/

共享组件说明用于
PumpStrategy 封装 pump(Duration) + 轮询,禁止 pumpAndSettle() Mock + 真实
safeTap() 先等 widget 出现,再点击,再 pump Mock + 真实
scrollToFind() 滚动查找 widget(带方向和最大次数) Mock + 真实
waitForWidget() 带超时的 widget 出现等待 Mock + 真实
BaseMockInterceptor Dio 拦截器基类,子类注册 App 特有路由 仅 Mock
FakeSecureStorage 内存 HashMap 实现的 SecureStorage 仅 Mock
BaseTestConfig 超时常量、baseUrl、通用配置 Mock + 真实
⚠️ 禁止 pumpAndSettle():所有三个 App 都有无限动画(CircularProgressIndicator、shimmer loading 等),pumpAndSettle() 会无限等待导致测试超时。统一使用 pump(Duration) + 轮询模式。

8. 总结决策树

你在测试什么?
├── UI 渲染是否正确? → Mock 测试
├── 边界/异常场景?(空数据、网络错误、超时) → Mock 测试
├── 前后端数据格式是否匹配? → 真实后端测试
├── 认证/权限是否正常? → 真实后端测试
├── 完整业务流程能否走通? → 真实后端测试
└── 两者都需要 → 两套都写

文档版本: v1.0 | 最后更新: 2026-02-06