Flutter Test with easy_localization and big translation json file
Solution 1
In the end, I fixed it by adding a file test/flutter_test_config.dart
. I got the inspiration from this issue.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/src/localization.dart';
import 'package:easy_localization/src/translations.dart';
import 'package:flutter/widgets.dart';
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
final content = await File('assets/lang/sv.json').readAsString(); // <- Or `ru.json`
final data = jsonDecode(content) as Map<String, dynamic>;
// easy_localization works with a singleton instance internally. We abuse
// this fact in tests and just let it load the English translations.
// Therefore we don't need to deal with any wrapper widgets and
// waiting/pumping in our widget tests.
Localization.load(
const Locale('en'),
translations: Translations(data),
);
await testMain();
}
Solution 2
This is because of how the json language file is loaded.
Take a look at this:
// from flutter/lib/src/services/asset_bundle.dart
Future<String> loadString(String key, { bool cache = true }) async {
final ByteData data = await load(key);
// Note: data has a non-nullable type, but might be null when running with
// weak checking, so we need to null check it anyway (and ignore the warning
// that the null-handling logic is dead code).
if (data == null)
throw FlutterError('Unable to load asset: $key'); // ignore: dead_code
// 50 KB of data should take 2-3 ms to parse on a Moto G4, and about 400 μs
// on a Pixel 4.
if (data.lengthInBytes < 50 * 1024) {
return utf8.decode(data.buffer.asUint8List());
}
// For strings larger than 50 KB, run the computation in an isolate to
// avoid causing main thread jank.
return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"');
}
If the file is bigger than a certain size, Flutter uses compute
to load it and avoid UI jank.
If in your test, you add this line before the await tester.pumpAndSettle()
, this will pause the main thread, give some time for the isolate to complete and resume execution.
await Future.delayed(const Duration(milliseconds: 150), () {}); // this line
await tester.pumpAndSettle()
On my laptop 150ms was the minimum duration to have the test pass consistently.
Valentin Vignal
Student in CentralSupélec and in the National University of Singapore
Updated on December 31, 2022Comments
-
Valentin Vignal over 1 year
I'm using easy_localization in a flutter project. I need to write some widget tests.
But it looks like that when the
.json
file with the translations is too big, theMaterialApp
never builds itschild
and therefore, I cannot test my widgets.Here is the architecture of my small reproducible project:
my_project |- assets | |- lang | | |- ru.json | | |- sv.json |- test | |- test_test.dart
Here is my
test_test.dart
file:import 'package:easy_localization/easy_localization.dart'; import 'package:easy_logger/easy_logger.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { testWidgets('', (tester) async { await tester.runAsync(() async { SharedPreferences.setMockInitialValues({}); EasyLocalization.logger.enableLevels = <LevelMessages>[ LevelMessages.error, LevelMessages.warning, ]; await EasyLocalization.ensureInitialized(); await tester.pumpWidget( EasyLocalization( supportedLocales: const [Locale('sv')], // <- Change it to 'ru' and it doesn't work path: 'assets/lang', child: Builder( builder: (context) { print('builder1'); return MaterialApp( locale: EasyLocalization.of(context).locale, supportedLocales: EasyLocalization.of(context).supportedLocales, localizationsDelegates: EasyLocalization.of(context).delegates, home: Builder( builder: (context) { print('builder2'); return const SizedBox.shrink(); }, ), ); }, ), ), ); await tester.pumpAndSettle(); }); }); }
sv.json
(a small file):{ "0": "0" }
ru.json
(a big file):{ "0": "0 - xx ... xx", // <- 1000 "x" "1": "1 - xx ... xx", // <- 1000 "x" // ... "3998": "3998 - xx ... xx", // <- 1000 "x" "3999": "3999 - xx ... xx" // <- 1000 "x" }
In my test, I have 2
print
s which should respectively printbuilder1
andbuilder2
.This works well when I use
Locale('sv')
in my test:00:02 +0: builder1 builder2 00:02 +1: All tests passed!
But when I use
Locale('ru')
,MaterialApp
doesn't build its child and I don't get the printbuilder2
:00:03 +0: builder1 00:03 +1: All tests passed!
How can I fix this?