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

为何单测在GUI单独执行通过,Flutter CLI执行却失败?

问题分析:JsonSerializable必填字段测试异常

我正在为包含可选参数的对象编写测试,验证其初始化规则,类定义如下:

part 'example.g.dart';

@JsonSerializable(createToJson: false)
class Example {
  String req1;
  String req2;
  String? opt;

  Example({required this.req1, required this.req2, this.opt});

  factory Example.fromJson(Map<String, dynamic> json) =>
      _$ExampleFromJson(json);
}

对应的自动生成的example.g.dart代码为:

Example _$ExampleFromJson(Map<String, dynamic> json) {
  return Example(
    req1: json['req1'] as String,
    req2: json['req2'] as String,
    opt: json['opt'] as String?,
  );
}

编写了以下两个测试用例,在Android Studio GUI中单独执行可通过,但使用flutter test命令行执行时失败:

test('should fail when json without req1 is provided', () {
  var testJson = {
    'req2': 'a-req2',
  };
  expect(() => Example.fromJson(testJson), throwsA(isA<TypeError>()));
});

test('should fail when json without req2 is provided', () {
  var testJson = {
    'req1': 'a-req1',
  };
  expect(() => Example.fromJson(testJson), throwsA(isA<TypeError>()));
});

Android Studio与终端的Dart版本不同(2.10和2.17.6),但在与GUI同版本的CI环境中执行测试也失败。管道执行时的错误信息如下:

Expected: throws <Instance of 'TypeError'>
    Actual: <Closure: () => Example>
     Which: returned <Instance of 'Example'>

即即使必填字段缺失,对象仍被实例化,请问这是什么原因?


解决方案

核心原因

  • 类型转换的行为差异:当json['req1']不存在时返回null,执行null as String在不同运行模式下表现不同:
    • Android Studio的测试通常运行在debug模式,类型检查严格,会直接抛出TypeError;
    • flutter test默认运行在优化后的模式,Dart的类型转换被弱化,null as String不会立即触发报错,而是将null赋值给非空的String字段,导致对象被错误实例化。
  • JsonSerializable默认不校验字段存在性:自动生成的代码仅做类型转换,不会检查必填字段是否存在于JSON中,缺失字段时的null赋值在宽松模式下不会触发异常。

修复步骤

  1. 启用JsonSerializable的严格校验:在@JsonSerializable注解中添加disallowUnrecognizedKeys: true,并为必填字段添加@JsonKey(required: true)标记:

    @JsonSerializable(createToJson: false, disallowUnrecognizedKeys: true)
    class Example {
      @JsonKey(required: true)
      String req1;
      @JsonKey(required: true)
      String req2;
      String? opt;
    
      Example({required this.req1, required this.req2, this.opt});
    
      factory Example.fromJson(Map<String, dynamic> json) =>
          _$ExampleFromJson(json);
    }
    

    重新生成代码后,fromJson会自动校验必填字段是否存在,缺失时会抛出MissingRequiredKeysException。

  2. 修改测试用例的断言目标:不再断言TypeError,改为断言MissingRequiredKeysException:

    import 'package:json_annotation/json_annotation.dart';
    
    test('should fail when json without req1 is provided', () {
      var testJson = {
        'req2': 'a-req2',
      };
      expect(() => Example.fromJson(testJson), throwsA(isA<MissingRequiredKeysException>()));
    });
    
  3. 统一测试运行模式:如果需要保持严格的类型检查,可以在运行测试时强制开启debug模式:

    flutter test --debug
    

内容的提问来源于stack exchange,提问作者gabrielrf97

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:48:45