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

Stream中列表内容一致但哈希码不同,Flutter测试如何通过?

流内容相等性校验问题解决方案

问题描述

我需要测试以下watchDebtPaybackList方法(返回Stream<Either<LoadDataFailure, List<DebtPaybackItem>>>):

@override
Stream<Either<LoadDataFailure, List<DebtPaybackItem>>> watchDebtPaybackList({
  required int personId,
  required int limit,
  DateFilter dateFilter = DateFilter.all,
}) async* {
  try {
    yield* _database.debtPaybackList(personId, limit).watch().map((result) {
      final debtPaybackList = result.map((e) => e.toDomain()).toList();
      return right(debtPaybackList);
    });
  } on DriftWrappedException {
    yield left(const LoadDataFailure.unexpected());
  }
}

编写的测试代码如下:

test('watchDebtPaybackList', () async {
  // Arrange
  final value = DebtPaybackData(
    id: 1,
    personId: 1,
    amount: 10,
    date: DateTime.now(),
    isDebt: 1,
  );

  when(() => mockDatabase.debtPaybackList(any(), any())).thenAnswer((_) {
    return MockSelectableData<DebtPaybackData>([value]);
  });

  // Act
  final stream = sud.watchDebtPaybackList(personId: 1, limit: 10);
  
  // Assert
  final exception = right<LoadDataFailure, List<DebtPaybackItem>>(
    [value.toDomain()],
  );

  print('$exception == $stream: ${exception.hashCode} == ${stream.hashCode}');

  expect(
    stream,
    emits(exception),
  );
});

测试始终失败,尽管流输出的列表内容与预期一致,但错误提示显示二者哈希码不同。尝试将Stream转为Future并使用equal匹配仍无效,请问如何忽略哈希码进行相等性校验?


解决方案

问题核心是默认的对象相等性校验依赖引用地址而非内容。以下是三种可行的解决方式:

1. 为DebtPaybackItem实现内容相等性

在DebtPaybackItem类中重写==运算符和hashCode,让校验基于对象的实际字段:

class DebtPaybackItem {
  final int id;
  final int personId;
  final double amount;
  final DateTime date;
  final bool isDebt;

  DebtPaybackItem({
    required this.id,
    required this.personId,
    required this.amount,
    required this.date,
    required this.isDebt,
  });

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is DebtPaybackItem &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          personId == other.personId &&
          amount == other.amount &&
          date.isAtSameMomentAs(other.date) &&
          isDebt == other.isDebt;

  @override
  int get hashCode => Object.hash(id, personId, amount, date, isDebt);
}

这样List<DebtPaybackItem>和Either的相等性都会基于内容判断,原测试代码的expect语句就能正常通过。

2. 使用predicate手动校验流内容

如果不想修改实体类,直接在测试中用predicate自定义校验逻辑:

expect(
  stream,
  emits(predicate<Either<LoadDataFailure, List<DebtPaybackItem>>>((event) {
    return event.fold(
      (failure) => false, // 失败分支不符合预期
      (items) {
        if (items.length != 1) return false;
        final expectedItem = value.toDomain();
        final actualItem = items.first;
        return actualItem.id == expectedItem.id &&
               actualItem.personId == expectedItem.personId &&
               actualItem.amount == expectedItem.amount &&
               actualItem.date.isAtSameMomentAs(expectedItem.date) &&
               actualItem.isDebt == expectedItem.isDebt;
      },
    );
  })),
);

3. 借助ListEquality校验列表

导入package:matcher,用ListEquality直接对比列表内容:

import 'package:matcher/matcher.dart';

// ...

expect(
  stream,
  emits(right(predicate<List<DebtPaybackItem>>((items) {
    return const ListEquality().equals(items, [value.toDomain()]);
  }))),
);

注意:校验DateTime时必须用isAtSameMomentAs,避免因毫秒/微秒差异导致校验失败。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:05:46