单独运行Google Fonts组件测试正常,批量运行失败的原因及解决方法
问题成因与修复方案
问题成因
- GoogleFonts库维护了全局的字体加载状态,批量运行测试时,第一个测试的状态会残留到第二个测试中,导致
GoogleFonts.pendingFonts()的等待逻辑异常 - 第二个测试里,
onEdit方法中的await GoogleFonts.pendingFonts()因全局状态污染无法正常完成,后续修改_isEnabled的setState代码永远不会执行,最终isEnabled停留在onLoad设置的true值 - 单独运行测试时没有跨测试的状态残留,
pendingFonts()能正常完成异步等待
修复方案
1. 为每个测试提供干净的初始化环境
将GoogleFonts的配置从setUpAll移到setUp,确保每个测试前都重新初始化配置,避免状态残留:
void main() { setUp(() async { GoogleFonts.config.allowRuntimeFetching = false; }); // 测试用例... }
2. 使用pumpAndSettle()等待所有异步任务完成
仅调用pump()无法确保所有异步任务(包括字体加载相关逻辑)执行完成,替换为pumpAndSettle()可以等待所有框架异步操作和重绘完成:
testWidgets('should handle edits', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SpecialTextWidget(shouldEdit: true), ), ); // 替换原有的await tester.pump() await tester.pumpAndSettle(); // 断言逻辑... });
3. 可选:强制重置GoogleFonts内部状态
如果上述方案仍有问题,可通过反射清除GoogleFonts的内部pending任务(仅测试环境使用):
import 'dart:mirrors'; setUp(() async { GoogleFonts.config.allowRuntimeFetching = false; // 重置pendingFonts的内部状态 final googleFontsMirror = reflect(GoogleFonts); final pendingCompletersField = googleFontsMirror.type.declarations[Symbol('_pendingFontsCompleters')] as VariableMirror; googleFontsMirror.setField(pendingCompletersField.simpleName, <Completer>{}); });
修改后的完整测试代码
// Flutter imports: import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_test/flutter_test.dart'; import 'package:google_fonts/google_fonts.dart'; void main() { setUp(() async { GoogleFonts.config.allowRuntimeFetching = false; }); testWidgets('should load', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SpecialTextWidget( shouldEdit: false, ), ), ); await tester.pumpAndSettle(); final textWidgetFinder = find.byType(SpecialTextWidget); final loadedState = tester.state(textWidgetFinder) as SpecialTextWidgetState; expect(textWidgetFinder, findsOneWidget); expect(loadedState.isEnabled, true); }); testWidgets('should handle edits', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: SpecialTextWidget( shouldEdit: true, ), ), ); await tester.pumpAndSettle(); final textWidgetFinder = find.byType(SpecialTextWidget); final editedState = tester.state(textWidgetFinder) as SpecialTextWidgetState; expect(textWidgetFinder, findsOneWidget); expect(editedState.isEnabled, false); }); } // 原SpecialTextWidget和SpecialTextWidgetState代码保持不变 class SpecialTextWidget extends StatefulWidget { final bool _shouldEdit; const SpecialTextWidget({ super.key, bool shouldEdit = false, }) : _shouldEdit = shouldEdit; @override State<SpecialTextWidget> createState() => SpecialTextWidgetState(); } class SpecialTextWidgetState extends State<SpecialTextWidget> { late bool _isInitialized; late bool _isEnabled; String? _data; TextStyle? _style; @visibleForTesting bool get isEnabled => _isEnabled; @override void initState() { _isInitialized = false; _isEnabled = false; super.initState(); } @override void didChangeDependencies() { if (!_isInitialized) { initialize(); _isInitialized = true; } super.didChangeDependencies(); } Future<void> initialize() async { await onLoad( fontFamily: 'Roboto', fontSize: 14.0, fontWeight: FontWeight.normal, fontStyle: FontStyle.normal, color: Colors.black, isEnabled: true, data: 'Hello', ); if (widget._shouldEdit) { await onEdit( fontFamily: 'Open Sans', fontSize: 16.0, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, color: Colors.white, isEnabled: false, data: 'Hello World', ); } } Future<void> onLoad({ required String fontFamily, required double fontSize, required FontWeight fontWeight, required FontStyle fontStyle, required Color color, required bool isEnabled, required String data, }) async { final textStyle = GoogleFonts.getFont( fontFamily, fontSize: fontSize, fontWeight: fontWeight, fontStyle: fontStyle, color: color, ); await GoogleFonts.pendingFonts(); setState(() { _isEnabled = isEnabled; _data = data; _style = textStyle; }); } Future<void> onEdit({ required String fontFamily, required double fontSize, required FontWeight fontWeight, required FontStyle fontStyle, required Color color, required bool isEnabled, required String data, }) async { var updatedStyle = _style; updatedStyle = GoogleFonts.getFont( fontFamily, fontSize: fontSize, fontWeight: fontWeight, fontStyle: fontStyle, color: color, ); await GoogleFonts.pendingFonts(); setState(() { _isEnabled = isEnabled; _data = data; _style = updatedStyle; }); } @override Widget build(BuildContext context) { if (!_isEnabled) { return SizedBox.shrink(); } return Text( _data!, style: _style, ); } }
内容的提问来源于stack exchange,提问作者anonymous-dev
相关产品推荐
相关产品推荐

