Тест на флаттер с easy_localization и большим переводом json-файла

Я использую easy_localization в проекте флаттера. Мне нужно написать несколько тестов виджетов.

Но похоже, что когда .json файл с переводами слишком велик, он никогда не строит его child и поэтому я не могу тестировать свои виджеты.

Вот архитектура моего небольшого воспроизводимого проекта:

      my_project
 |- assets
 |   |- lang
 |   |   |- ru.json
 |   |   |- sv.json
 |- test
 |   |- test_test.dart

Вот мой test_test.dart файл:

      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 (небольшой файл):

      {
  "0": "0"
}

ru.json (большой файл):

      {
  "0": "0 - xx ... xx",  // <- 1000 "x"
  "1": "1 - xx ... xx",  // <- 1000 "x"
  // ...
  "3998": "3998 - xx ... xx",  // <- 1000 "x"
  "3999": "3999 - xx ... xx"  // <- 1000 "x"
}

В моем тесте у меня 2 prints, который должен соответственно печатать builder1 и .

Это хорошо работает, когда я использую Locale('sv') в моем тесте:

      00:02 +0:                                                                                                                                                                                                                                               
builder1
builder2
00:02 +1: All tests passed!

Но когда я использую Locale('ru'), MaterialApp не строит своего ребенка, и я не получаю отпечаток builder2:

      00:03 +0:                                                                                                                                                                                                                                               
builder1
00:03 +1: All tests passed!

Как я могу это исправить?

3 ответа

Решение

В итоге исправил, добавив файл test/flutter_test_config.dart. Я почерпнул вдохновение из этого выпуска .

      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();
}

Это связано с тем, как загружается языковой файл json.
Взгляните на это:

      // 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"');
}

Если файл больше определенного размера, Flutter использует compute чтобы загрузить его и избежать рывков пользовательского интерфейса.

Если в вашем тесте вы добавляете эту строку перед await tester.pumpAndSettle(), это приостановит основной поток, даст некоторое время для завершения изоляции и возобновления выполнения.

      await Future.delayed(const Duration(milliseconds: 150), () {}); // this line
await tester.pumpAndSettle()

На моем ноутбуке 150 мс были минимальной продолжительностью для стабильного прохождения теста.

Когда вы работаете с CodegenLoader, это небольшой метод, который вы можете использовать для настройки простого синглтона локализации, аналогичного примеру json от Valentin Vignal, я бы также предложил использовать небольшие функции без параметров обратного вызова вместо вложения ваших функций настройки, по крайней мере, если вы не работают с самим обратным вызовом. Таким образом, становится ясно, что (только!) имеет место побочный эффект.

      void addLocalization() {
  Localization.load(
    const Locale('en'),
    translations: Translations(CodegenLoader.en),
  );
}
Другие вопросы по тегам