Flutter自定义LocalizationDelegate导致Widget测试失败问题排查
问题描述
自定义了处理应用本地化的LocalizationDelegate,代码如下:
class LocalizationDelegate extends LocalizationsDelegate<Localization> { const LocalizationDelegate(); @override bool isSupported(Locale locale) => ['en', 'fr'].contains(locale.languageCode); @override Future<Localization> load(Locale locale) async { String string = await rootBundle .loadString("assets/strings/${locale.languageCode}.json"); language = json.decode(string); return SynchronousFuture<Localization>(Localization()); } @override bool shouldReload(LocalizationDelegate old) => false; }
运行Widget测试时抛出错误,测试代码:
testWidgets('Localization test', (WidgetTester tester) async { await tester.pumpWidget(const FakeTestPage()); expect(find.text('TEST'), findsOneWidget); print("Done !"); });
FakeTestPage代码:
class FakeTestPage extends StatelessWidget { const FakeTestPage({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( localizationsDelegates: [ LocalizationDelegate(), GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: [Locale("fr"), Locale("en")], locale: Locale("fr"), home: Scaffold( body: Text("TEST"), ), ); } }
错误日志:
/Users/foxtom/Desktop/flutter/bin/flutter --no-color test --machine --start-paused test/localization_test.dart Testing started at 11:07 ... ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure was thrown running a test: Expected: exactly one matching node in the widget tree Actual: _TextFinder:<zero widgets with text "TEST" (ignoring offstage widgets)> Which: means none were found but one was expected When the exception was thrown, this was the stack: #4 main.<anonymous closure> (file:///Users/foxtom/StudioProjects/TestProject/test/localization_test.dart:12:5) <asynchronous suspension> <asynchronous suspension> (elided one frame from package:stack_trace) This was caught by the test expectation on the following line: file:///Users/foxtom/StudioProjects/TestProject/test/localization_test.dart line 12 The test description was: Localization test ════════════════════════════════════════════════════════════════════════════════════════════════════ Test failed. See exception logs above. The test description was: Localization test
移除localizationsDelegates中的LocalizationDelegate()后测试可正常运行,疑问:是否是Delegate的Future部分存在问题?如何让测试与实际应用一样正常运行?
问题原因与解决方法
核心原因
测试失败的关键有两点:
- 测试环境未加载assets资源:默认情况下Flutter测试不会自动加载
assets目录下的文件,rootBundle.loadString会执行失败,导致本地化初始化出错,阻塞了MaterialApp的渲染流程,最终目标Text("TEST")未被正确渲染。 - 异步逻辑未被正确等待:虽然
load方法最后返回了SynchronousFuture,但之前的await rootBundle.loadString是真实异步操作,测试中没有等待资源加载完成就执行了断言。
解决步骤
步骤1:配置测试环境加载assets
在测试文件中添加初始化逻辑,注册并预加载所需的本地化资源:
import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { setUp(() async { // 确保测试绑定初始化 await TestWidgetsFlutterBinding.ensureInitialized(); // 预加载测试用到的本地化文件 await rootBundle.loadString('assets/strings/fr.json'); }); testWidgets('Localization test', (WidgetTester tester) async { await tester.pumpWidget(const FakeTestPage()); // 等待异步操作完成并触发UI重绘 await tester.pumpAndSettle(); expect(find.text('TEST'), findsOneWidget); print("Done !"); }); }
步骤2:给LocalizationDelegate添加容错处理
在load方法中捕获资源加载异常,避免崩溃阻塞UI渲染:
@override Future<Localization> load(Locale locale) async { try { String string = await rootBundle .loadString("assets/strings/${locale.languageCode}.json"); language = json.decode(string); } catch (e) { // 加载失败时使用空字典或默认值 language = {}; print("本地化文件加载失败: $e"); } return SynchronousFuture<Localization>(Localization()); }
步骤3:确认测试用本地化文件存在
确保assets/strings/fr.json文件在项目中存在且格式正确,测试环境需要能正常读取该文件。
可选优化:Mock本地化Delegate
如果只是测试UI而非本地化逻辑,可以Mock一个跳过真实资源加载的Delegate,让测试更高效稳定:
class MockLocalizationDelegate extends LocalizationsDelegate<Localization> { const MockLocalizationDelegate(); @override bool isSupported(Locale locale) => true; @override Future<Localization> load(Locale locale) async { language = {}; return SynchronousFuture<Localization>(Localization()); } @override bool shouldReload(MockLocalizationDelegate old) => false; }
然后在FakeTestPage的localizationsDelegates中替换为MockLocalizationDelegate()即可。
内容的提问来源于stack exchange,提问作者Tom3652
相关产品推荐
相关产品推荐

