flutter-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter Testing

Flutter 测试

Overview

概述

This skill provides comprehensive guidance for testing Flutter applications across all test types. Flutter testing falls into three categories:
  • Unit tests - Test individual functions, methods, or classes in isolation
  • Widget tests (component tests) - Test single widgets and verify UI appearance and behavior
  • Integration tests - Test complete apps or large parts to verify end-to-end functionality
A well-tested Flutter app has many unit and widget tests for code coverage, plus enough integration tests to cover important use cases.
本指南为Flutter应用的各类测试提供全面指导。Flutter测试分为三类:
  • Unit tests - 独立测试单个函数、方法或类
  • Widget tests(组件测试)- 测试单个Widget并验证UI外观与行为
  • Integration tests - 测试完整应用或大型模块以验证端到端功能
一个测试完善的Flutter应用会包含大量单元测试和Widget测试以保证代码覆盖率,同时搭配足够的集成测试覆盖核心用户场景。

Test Type Trade-offs

测试类型权衡

TradeoffUnitWidgetIntegration
ConfidenceLowHigherHighest
Maintenance costLowHigherHighest
DependenciesFewMoreMost
Execution speedQuickQuickSlow
权衡维度Unit测试Widget测试Integration测试
测试可信度较高最高
维护成本较高最高
依赖项数量较多最多
执行速度快速快速缓慢

Build Modes for Testing

测试用构建模式

Flutter supports three build modes with different implications for testing:
  • Debug mode - Use during development with hot reload. Assertions enabled, debugging enabled, but performance is janky
  • Profile mode - Use for performance analysis. Similar to release mode but with some debugging features enabled
  • Release mode - Use for deployment. Assertions disabled, optimized for speed and size
Flutter支持三种构建模式,各模式对测试的影响不同:
  • Debug mode - 开发阶段使用,支持热重载。启用断言和调试功能,但性能表现一般
  • Profile mode - 用于性能分析。与Release模式类似,但保留部分调试功能
  • Release mode - 用于部署。禁用断言,针对速度和体积进行优化

Quick Start

快速入门

Unit Tests

Unit Tests

Unit tests test a single function, method, or class. Mock external dependencies and avoid disk I/O or UI rendering.
dart
import 'package:test/test.dart';
import 'package:my_app/counter.dart';

void main() {
  test('Counter value should be incremented', () {
    final counter = Counter();
    counter.increment();
    expect(counter.value, 1);
  });
}
Run with:
flutter test
Unit tests用于测试单个函数、方法或类。模拟外部依赖项,避免磁盘I/O或UI渲染。
dart
import 'package:test/test.dart';
import 'package:my_app/counter.dart';

void main() {
  test('Counter value should be incremented', () {
    final counter = Counter();
    counter.increment();
    expect(counter.value, 1);
  });
}
运行命令:
flutter test

Widget Tests

Widget Tests

Widget tests test single widgets to verify UI appearance and interaction.
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
    
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
    
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
  });
}
Widget Tests用于测试单个Widget,验证其UI外观与交互逻辑。
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
    
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
    
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
  });
}

Integration Tests

Integration Tests

Integration tests test complete apps on real devices or emulators.
dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('tap button, verify counter', (tester) async {
    await tester.pumpWidget(const MyApp());
    expect(find.text('0'), findsOneWidget);
    
    await tester.tap(find.byKey(const ValueKey('increment')));
    await tester.pumpAndSettle();
    
    expect(find.text('1'), findsOneWidget);
  });
}
Run with:
flutter test integration_test/
Integration Tests用于在真实设备或模拟器上测试完整应用。
dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('tap button, verify counter', (tester) async {
    await tester.pumpWidget(const MyApp());
    expect(find.text('0'), findsOneWidget);
    
    await tester.tap(find.byKey(const ValueKey('increment')));
    await tester.pumpAndSettle();
    
    expect(find.text('1'), findsOneWidget);
  });
}
运行命令:
flutter test integration_test/

Testing Workflow Decision Tree

测试工作流决策树

  1. What are you testing?
  2. Does it depend on plugins/native code?
  3. Need to mock dependencies?
    • Yes → See Mocking Guide
  4. Encountering errors?
    • See Common Testing Errors
  1. 你要测试什么?
  2. 是否依赖插件/原生代码?
  3. 是否需要模拟依赖项?
    • 是 → 查看模拟指南
  4. 遇到错误?

Unit Tests

Unit Tests

Unit tests verify the correctness of a unit of logic under various conditions.
Unit Tests用于验证逻辑单元在不同场景下的正确性。

When to Use Unit Tests

适用场景

  • Testing business logic functions
  • Validating data transformations
  • Testing state management logic
  • Mocking external services/API calls
  • 测试业务逻辑函数
  • 验证数据转换逻辑
  • 测试状态管理逻辑
  • 模拟外部服务/API调用

Key Concepts

核心概念

  • Use
    package:test/test.dart
  • Mock dependencies using Mockito or similar
  • Avoid file I/O or UI rendering
  • Fast execution, high maintainability
  • 使用
    package:test/test.dart
  • 使用Mockito等工具模拟依赖项
  • 避免文件I/O或UI渲染
  • 执行速度快,维护成本低

Advanced Unit Testing

进阶Unit测试

For mocking dependencies, plugin interactions, and complex scenarios, see Unit Testing Reference.
关于依赖项模拟、插件交互及复杂场景测试,查看Unit测试参考

Widget Tests

Widget Tests

Widget tests verify widget UI appearance and behavior in a test environment.
Widget Tests用于在测试环境中验证Widget的UI外观与行为。

When to Use Widget Tests

适用场景

  • Testing widget rendering
  • Verifying user interactions (taps, drags, scrolling)
  • Testing different orientations
  • Validating widget state changes
  • 测试Widget渲染效果
  • 验证用户交互(点击、拖拽、滚动)
  • 测试不同屏幕方向
  • 验证Widget状态变化

Widget Testing Patterns

Widget测试模式

Finding Widgets

查找Widget

dart
// By text
final titleFinder = find.text('Title');

// By widget type
final buttonFinder = find.byType(ElevatedButton);

// By key
final fabFinder = find.byKey(const ValueKey('increment'));

// By widget instance
final myWidgetFinder = find.byWidget(myWidgetInstance);
dart
// 通过文本查找
final titleFinder = find.text('Title');

// 通过Widget类型查找
final buttonFinder = find.byType(ElevatedButton);

// 通过Key查找
final fabFinder = find.byKey(const ValueKey('increment'));

// 通过Widget实例查找
final myWidgetFinder = find.byWidget(myWidgetInstance);

User Interactions

用户交互模拟

dart
// Tap
await tester.tap(buttonFinder);

// Drag
await tester.drag(listFinder, const Offset(0, -300));

// Enter text
await tester.enterText(fieldFinder, 'Hello World');

// Scroll
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
dart
// 点击
await tester.tap(buttonFinder);

// 拖拽
await tester.drag(listFinder, const Offset(0, -300));

// 输入文本
await tester.enterText(fieldFinder, 'Hello World');

// 滚动
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();

Testing Different Orientations

测试不同屏幕方向

dart
testWidgets('widget in landscape mode', (tester) async {
  // Set to landscape
  await tester.binding.setSurfaceSize(const Size(800, 400));
  await tester.pumpWidget(const MyApp());
  
  // Verify landscape behavior
  expect(find.byType(MyWidget), findsOneWidget);
  
  // Reset to portrait
  addTearDown(tester.binding.setSurfaceSize(null));
});
dart
testWidgets('widget in landscape mode', (tester) async {
  // 设置为横屏模式
  await tester.binding.setSurfaceSize(const Size(800, 400));
  await tester.pumpWidget(const MyApp());
  
  // 验证横屏行为
  expect(find.byType(MyWidget), findsOneWidget);
  
  // 重置为竖屏
  addTearDown(tester.binding.setSurfaceSize(null));
});

Advanced Widget Testing

进阶Widget测试

For scrolling, complex interactions, and performance testing, see Widget Testing Reference.
关于滚动测试、复杂交互及性能测试,查看Widget测试参考

Integration Tests

Integration Tests

Integration tests test complete apps or large parts on real devices or emulators.
Integration Tests用于在真实设备或模拟器上测试完整应用或大型模块。

When to Use Integration Tests

适用场景

  • Testing complete user flows
  • Verifying multiple screens/pages
  • Testing navigation flows
  • Performance profiling
  • 测试完整用户流程
  • 验证多页面交互
  • 测试导航流程
  • 性能分析

Integration Test Structure

Integration测试结构

dart
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('end-to-end test', () {
    testWidgets('complete user flow', (tester) async {
      await tester.pumpWidget(const MyApp());
      
      // Step 1: Navigate to screen
      await tester.tap(find.text('Login'));
      await tester.pumpAndSettle();
      
      // Step 2: Enter credentials
      await tester.enterText(find.byKey(const Key('username')), 'user');
      await tester.enterText(find.byKey(const Key('password')), 'pass');
      
      // Step 3: Submit
      await tester.tap(find.text('Submit'));
      await tester.pumpAndSettle();
      
      // Verify result
      expect(find.text('Welcome'), findsOneWidget);
    });
  });
}
dart
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('end-to-end test', () {
    testWidgets('complete user flow', (tester) async {
      await tester.pumpWidget(const MyApp());
      
      // 步骤1:导航至登录页
      await tester.tap(find.text('Login'));
      await tester.pumpAndSettle();
      
      // 步骤2:输入凭证
      await tester.enterText(find.byKey(const Key('username')), 'user');
      await tester.enterText(find.byKey(const Key('password')), 'pass');
      
      // 步骤3:提交表单
      await tester.tap(find.text('Submit'));
      await tester.pumpAndSettle();
      
      // 验证结果
      expect(find.text('Welcome'), findsOneWidget);
    });
  });
}

Performance Testing

性能测试

dart
testWidgets('scrolling performance', (tester) async {
  await tester.pumpWidget(const MyApp());
  
  final listFinder = find.byType(ListView);
  
  // Measure performance
  final timeline = await tester.trace(() async {
    await tester.fling(listFinder, const Offset(0, -500), 10000);
    await tester.pumpAndSettle();
  });
  
  // Analyze timeline data
  expect(timeline.frames.length, greaterThan(10));
});
dart
testWidgets('scrolling performance', (tester) async {
  await tester.pumpWidget(const MyApp());
  
  final listFinder = find.byType(ListView);
  
  // 测量性能
  final timeline = await tester.trace(() async {
    await tester.fling(listFinder, const Offset(0, -500), 10000);
    await tester.pumpAndSettle();
  });
  
  // 分析时间线数据
  expect(timeline.frames.length, greaterThan(10));
});

Advanced Integration Testing

进阶Integration测试

For performance profiling, CI integration, and complex scenarios, see Integration Testing Reference.
关于性能分析、CI集成及复杂场景测试,查看Integration测试参考

Plugins in Tests

测试中的插件处理

When testing code that uses plugins, special handling is required to avoid crashes.
测试使用插件的代码时,需要特殊处理以避免崩溃。

Testing App Code with Plugins

测试使用插件的应用代码

If your Flutter app uses plugins, you need to mock the platform channel calls in unit/widget tests.
dart
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  
  setUp(() {
    // Mock platform channel
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(
      const MethodChannel('your.plugin.channel'),
      (MethodCall methodCall) async {
        if (methodCall.method == 'getPlatformVersion') {
          return 'Android 12';
        }
        return null;
      },
    );
  });
  
  tearDown(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(
      const MethodChannel('your.plugin.channel'),
      null,
    );
  });
}
如果你的Flutter应用使用了插件,在单元/Widget测试中需要模拟平台通道调用。
dart
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  
  setUp(() {
    // 模拟平台通道
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(
      const MethodChannel('your.plugin.channel'),
      (MethodCall methodCall) async {
        if (methodCall.method == 'getPlatformVersion') {
          return 'Android 12';
        }
        return null;
      },
    );
  });
  
  tearDown(() {
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(
      const MethodChannel('your.plugin.channel'),
      null,
    );
  });
}

Testing Plugins

插件测试

For comprehensive guidance on testing Flutter plugins (including native code), see Plugin Testing Reference.
关于Flutter插件(包含原生代码)的全面测试指导,查看插件测试参考

Common Testing Errors

常见测试错误

'A RenderFlex overflowed...'

'A RenderFlex overflowed...'

Yellow and black stripes indicate overflow. Usually caused by unconstrained children in Row/Column.
Solution: Wrap the overflowing widget in
Expanded
or
Flexible
.
dart
// Problem
Row(
  children: [
    Icon(Icons.message),
    Column(children: [Text('Very long text...')]), // Overflow!
  ],
)

// Solution
Row(
  children: [
    Icon(Icons.message),
    Expanded(child: Column(children: [Text('Very long text...')])),
  ],
)
黄黑条纹表示布局溢出,通常是Row/Column中的子组件未受约束导致。
解决方案: 将溢出的Widget包裹在
Expanded
Flexible
中。
dart
// 问题代码
Row(
  children: [
    Icon(Icons.message),
    Column(children: [Text('Very long text...')]), // 溢出!
  ],
)

// 修复代码
Row(
  children: [
    Icon(Icons.message),
    Expanded(child: Column(children: [Text('Very long text...')])),
  ],
)

'Vertical viewport was given unbounded height'

'Vertical viewport was given unbounded height'

Occurs when ListView (or other scrollable) is inside Column without height constraints.
Solution: Wrap in
Expanded
or use
shrinkWrap: true
.
dart
// Problem
Column(
  children: [
    Text('Header'),
    ListView(children: [...]), // Error!
  ],
)

// Solution
Column(
  children: [
    Text('Header'),
    Expanded(child: ListView(children: [...])),
  ],
)
当ListView(或其他可滚动组件)被嵌套在无高度约束的Column中时会触发该错误。
解决方案: 包裹在
Expanded
中或设置
shrinkWrap: true
dart
// 问题代码
Column(
  children: [
    Text('Header'),
    ListView(children: [...]), // 错误!
  ],
)

// 修复代码
Column(
  children: [
    Text('Header'),
    Expanded(child: ListView(children: [...])),
  ],
)

'setState called during build'

'setState called during build'

Never call setState during build method.
Solution: Use Navigator API or defer to post-build callback.
For more errors and solutions, see Common Errors Reference.
绝不能在build方法中调用setState。
解决方案: 使用Navigator API或延迟到build完成后再执行。
更多错误及解决方案,查看常见错误参考

Testing Best Practices

测试最佳实践

  1. Test Pyramid - More unit/widget tests, fewer integration tests
  2. Descriptive Test Names - Names should clearly describe what and why
  3. Arrange-Act-Assert - Structure tests with clear sections
  4. Avoid Test Interdependence - Each test should be independent
  5. Mock External Dependencies - Keep tests fast and reliable
  6. Run Tests in CI - Automate testing on every push
  1. 测试金字塔 - 多编写单元/Widget测试,减少集成测试数量
  2. 清晰的测试命名 - 测试名称应明确描述测试内容与目的
  3. Arrange-Act-Assert结构 - 按准备-执行-断言的结构组织测试
  4. 测试独立性 - 每个测试应独立运行,不依赖其他测试结果
  5. 模拟外部依赖 - 保证测试快速且可靠
  6. CI中自动运行测试 - 每次代码推送时自动执行测试

Running Tests

运行测试

Run All Tests

运行所有测试

bash
flutter test
bash
flutter test

Run Specific Test File

运行指定测试文件

bash
flutter test test/widget_test.dart
bash
flutter test test/widget_test.dart

Run Integration Tests

运行集成测试

bash
flutter test integration_test/
bash
flutter test integration_test/

Run with Coverage

生成测试覆盖率报告

bash
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
bash
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html

Run Tests on Different Platforms

在不同平台运行测试

bash
undefined
bash
undefined

Android

Android平台

flutter test --platform android
flutter test --platform android

iOS

iOS平台

flutter test --platform ios
flutter test --platform ios

Web

Web平台

flutter test --platform chrome
undefined
flutter test --platform chrome
undefined

Debugging Tests

调试测试

Debug a Test

调试单个测试

bash
flutter test --no-sound-null-safety test/my_test.dart
bash
flutter test --no-sound-null-safety test/my_test.dart

Verbose Output

详细输出模式

bash
flutter test --verbose
bash
flutter test --verbose

Run Specific Test

运行指定名称的测试

bash
flutter test --name "Counter value should be incremented"
bash
flutter test --name "Counter value should be incremented"

Resources

资源

Reference Files

参考文档

  • Unit Testing Guide - In-depth unit testing patterns and mocking strategies
  • Widget Testing Guide - Widget finding, interactions, and advanced scenarios
  • Integration Testing Guide - End-to-end testing and performance profiling
  • Mocking Guide - Mocking dependencies and plugin interactions
  • Common Errors - Solutions for frequent testing errors
  • Plugin Testing - Testing Flutter plugins with native code
  • Unit测试指南 - 深入的单元测试模式与模拟策略
  • Widget测试指南 - Widget查找、交互及进阶场景
  • Integration测试指南 - 端到端测试与性能分析
  • 模拟指南 - 依赖项与插件交互模拟
  • 常见错误 - 常见测试错误解决方案
  • 插件测试 - 包含原生代码的Flutter插件测试

External Resources

外部资源