dart-matcher-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dart Matcher Best Practices

Dart Matcher 最佳实践

When to use this skill

什么时候使用本技能

Use this skill when:
  • Writing assertions using
    expect
    and
    package:matcher
    .
  • Migrating legacy manual checks to cleaner matchers.
  • Debugging confusing test failures.
当你遇到以下场景时可使用本技能:
  • 使用
    expect
    package:matcher
    编写断言时
  • 将遗留的手动校验逻辑迁移为更简洁的匹配器实现时
  • 排查令人困惑的测试失败问题时

Core Matchers

核心匹配器

1. Collections (
hasLength
,
contains
,
isEmpty
,
unorderedEquals
,
containsPair
)

1. 集合类匹配器(
hasLength
contains
isEmpty
unorderedEquals
containsPair

  • hasLength(n)
    :
    • Prefer
      expect(list, hasLength(n))
      over
      expect(list.length, n)
      .
    • Gives better error messages on failure (shows actual list content).
  • isEmpty
    /
    isNotEmpty
    :
    • Prefer
      expect(list, isEmpty)
      over
      expect(list.isEmpty, true)
      .
    • Prefer
      expect(list, isNotEmpty)
      over
      expect(list.isNotEmpty, true)
      .
  • contains(item)
    :
    • Verify existence without manual iteration.
    • Prefer over
      expect(list.contains(item), true)
      .
  • unorderedEquals(items)
    :
    • Verify contents regardless of order.
    • Prefer over
      expect(list, containsAll(items))
      .
  • containsPair(key, value)
    :
    • Verify a map contains a specific key-value pair.
    • Prefer over checking
      expect(map[key], value)
      or
      expect(map.containsKey(key), true)
      .
  • hasLength(n)
    • 优先使用
      expect(list, hasLength(n))
      而非
      expect(list.length, n)
    • 失败时会提供更友好的错误信息(会展示列表的实际内容)
  • isEmpty
    /
    isNotEmpty
    • 优先使用
      expect(list, isEmpty)
      而非
      expect(list.isEmpty, true)
    • 优先使用
      expect(list, isNotEmpty)
      而非
      expect(list.isNotEmpty, true)
  • contains(item)
    • 无需手动遍历即可校验元素是否存在
    • 优先使用它代替
      expect(list.contains(item), true)
  • unorderedEquals(items)
    • 不考虑顺序校验集合内容
    • 优先使用它代替
      expect(list, containsAll(items))
  • containsPair(key, value)
    • 校验Map中是否包含指定的键值对
    • 优先使用它代替
      expect(map[key], value)
      或者
      expect(map.containsKey(key), true)
      的写法

2. Type Checks (
isA<T>
and
TypeMatcher<T>
)

2. 类型校验(
isA<T>
TypeMatcher<T>

  • isA<T>()
    :
    • Prefer for inline assertions:
      expect(obj, isA<Type>())
      .
    • More concise and readable than
      TypeMatcher<Type>()
      .
    • Allows chaining constraints using
      .having()
      .
  • TypeMatcher<T>
    :
    • Prefer when defining top-level reusable matchers.
    • Use
      const
      :
      const isMyType = TypeMatcher<MyType>();
    • Chaining
      .having()
      works here too, but the resulting matcher is not
      const
      .
  • isA<T>()
    • 优先用于行内断言:
      expect(obj, isA<Type>())
    • TypeMatcher<Type>()
      更简洁易读
    • 支持通过
      .having()
      链式添加约束
  • TypeMatcher<T>
    • 优先用于定义顶层可复用的匹配器
    • 使用
      const
      修饰
      const isMyType = TypeMatcher<MyType>();
    • 同样支持链式调用
      .having()
      ,但生成的匹配器不支持
      const
      修饰

3. Object Properties (
having
)

3. 对象属性校验(
having

Use
.having()
on
isA<T>()
or other TypeMatchers to check properties.
  • Descriptive Names: Use meaningful parameter names in the closure (e.g.,
    (e) => e.message
    ) instead of generic ones like
    p0
    to improve readability.
dart
expect(person, isA<Person>()
    .having((p) => p.name, 'name', 'Alice')
    .having((p) => p.age, 'age', greaterThan(18)));
This provides detailed failure messages indicating exactly which property failed.
isA<T>()
或其他TypeMatcher上使用
.having()
来校验属性。
  • 描述性命名:在闭包中使用有意义的参数名(例如
    (e) => e.message
    )而非
    p0
    这类泛用名称,提升可读性。
dart
expect(person, isA<Person>()
    .having((p) => p.name, 'name', 'Alice')
    .having((p) => p.age, 'age', greaterThan(18)));
这种写法会生成详细的失败信息,明确指出具体是哪个属性校验不通过。

4. Async Assertions

4. 异步断言

  • completion(matcher)
    :
    • Wait for a future to complete and check its value.
    • Prefer
      await expectLater(...)
      to ensure the future completes before the test continues.
    • await expectLater(future, completion(equals(42)))
      .
  • throwsA(matcher)
    :
    • Check that a future or function throws an exception.
    • await expectLater(future, throwsA(isA<StateError>()))
      .
    • expect(() => function(), throwsA(isA<ArgumentError>()))
      (synchronous function throwing is fine with
      expect
      ).
  • completion(matcher)
    • 等待Future完成并校验其返回值
    • 优先使用
      await expectLater(...)
      ,确保测试继续执行前Future已完成
    • 示例:
      await expectLater(future, completion(equals(42)))
  • throwsA(matcher)
    • 校验Future或函数是否会抛出异常
    • 示例:
      await expectLater(future, throwsA(isA<StateError>()))
    • 同步函数抛出异常的场景直接用
      expect
      即可:
      expect(() => function(), throwsA(isA<ArgumentError>()))

5. Using
expectLater

5.
expectLater
的使用

Use
await expectLater(...)
when testing async behavior to ensure proper sequencing.
dart
// GOOD: Waits for future to complete before checking side effects
await expectLater(future, completion(equals(42)));
expect(sideEffectState, equals('done'));

// BAD: Side effect check might run before future completes
expect(future, completion(equals(42)));
expect(sideEffectState, equals('done')); // Race condition!
测试异步行为时使用
await expectLater(...)
,确保执行顺序正确。
dart
// 推荐写法:校验副作用前先等待Future执行完成
await expectLater(future, completion(equals(42)));
expect(sideEffectState, equals('done'));

// 不推荐写法:副作用校验可能在Future完成前就执行
expect(future, completion(equals(42)));
expect(sideEffectState, equals('done')); // 竞态条件!

Principles

原则

  1. Readable Failures: Choose matchers that produce clear error messages.
  2. Avoid Manual Logic: Don't use
    if
    statements or
    for
    loops for assertions; let matchers handle it.
  3. Specific Matchers: Use the most specific matcher available (e.g.,
    containsPair
    for maps instead of checking keys manually).
  1. 失败信息可读性:选择能生成清晰错误信息的匹配器
  2. 避免手动逻辑:不要在断言中使用
    if
    语句或
    for
    循环,交给匹配器处理即可
  3. 匹配器专一性:使用最贴合场景的特定匹配器(例如针对Map使用
    containsPair
    ,而非手动校验键)

Related Skills

相关技能