You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单独运行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 08:42:02