agentleFS
kevmookevmoo/dash_skills/skills/dart-matcher-best-practices/SKILL.md

dart-matcher-best-practices

Skill148 starsChanged 13 days agoApache-2.0View on GitHub

Best practices for using `expect` and `package:matcher`. Focuses on readable assertions, proper matcher selection, and avoiding common pitfalls.

The file

Show its rules and text

We split this skill into the rules it gives an agent

19 rules. About 0 are common, 9 are distinctive, and 10 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill when:

    line 18
  2. Distinctive:

    Do NOT apply this skill or refactor assertions when:

    line 26
  3. Very specific:

    Search for length checks that should use hasLength:

    line 47
  4. Distinctive:

    Search for checks on boolean properties that have specific matchers:

    line 53
  5. Very specific:

    Search for manual map lookups instead of containsPair:

    line 61
  6. Very specific:

    Prefer expect(list, hasLength(n)) over expect(list.length, n).

    line 70
  7. Distinctive:

    Verify existence without manual iteration.

    line 78
  8. Distinctive:

    Verify contents regardless of order.

    line 82
  9. Distinctive:

    Verify a map contains a specific key-value pair.

    line 86
  10. Very specific:

    Prefer over checking expect(map[key], value) or expect(map.containsKey(key), true).

    line 87
  11. Very specific:

    Prefer for inline assertions: expect(obj, isA<Type>()).

    line 93
  12. Distinctive:

    Prefer when defining top-level reusable matchers.

    line 98
  13. Very specific:

    Use .having() on isA<T>() or other TypeMatchers to check properties.

    line 105
  14. Very specific:

    Descriptive Names: Use meaningful parameter names in the closure (e.g., (e) => e.message) instead of generic ones like p0 to improve …

    line 107
  15. Distinctive:

    Wait for a future to complete and check its value.

    line 122
  16. Distinctive:

    Check that a future or function throws an exception.

    line 128
  17. Very specific:

    Use await expectLater(...) when testing async behavior to ensure proper sequencing.

    line 152
  18. Very specific:

    Avoid Manual Logic: Don't use if statements or for loops for assertions.

    line 168
  19. Very specific:

    Specific Matchers: Use the most specific matcher available (e.g., containsPair for maps instead of checking keys manually).

    line 170

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Dart Matcher Best Practices

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.

When NOT to use (Abstention Guardrails)

Do NOT apply this skill or refactor assertions when:

  • Non-Collection Properties: The property happens to be named .length but belongs to a non-collection class (e.g. geometric scalar magnitude on a Vector, duration on a buffer, or line segment distance). hasLength(n) expects an int on an Iterable, Map, or String.
  • Custom Domain Booleans: The boolean check tests an arbitrary domain property (e.g. expect(switch.isEnabled, isTrue)). Do not invent non-existent matchers.
  • Alternative Assertion Frameworks: The package has adopted package:checks (check(x)...). Do not revert checks back to package:matcher.
  • Already Idiomatic Assertions: Tests already use declarative matchers (hasLength, isEmpty, isNotEmpty, contains, containsPair, isA, throwsA). Abstain and make zero modifications.

Discovery

To find candidates for improving matcher usage, search for suboptimal patterns:

Suboptimal Length Checks

Search for length checks that should use hasLength:

  • Regex: expect\([^,]+.length,\s*

Suboptimal Boolean Checks

Search for checks on boolean properties that have specific matchers:

  • Regex: expect\([^,]+.isEmpty,\s*(true|equals\(true\))
  • Regex: expect\([^,]+.isNotEmpty,\s*(true|equals\(true\))
  • Regex: expect\([^,]+.contains\(.*\),\s*(true|equals\(true\))

Suboptimal Map Lookups

Search for manual map lookups instead of containsPair:

  • Regex: expect\([^,]+\[.*\],\s*

Core Matchers

1. Collections (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).

2. Type Checks (isA<T> and 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.

3. Object Properties (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.
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.

4. Async Assertions

  • 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).

5. Exception Testing (Avoid try/catch with fail())

  • Prefer throwsA over imperative try/catch:
    • Legacy tests often use try { fn(); fail('should throw'); } catch (e) { expect(e, isA<T>()); }.
    • Replace with declarative matchers:
      // Synchronous:
      expect(() => parseData('invalid'), throwsA(isA<FormatException>()));
      
      // Asynchronous:
      await expectLater(client.fetch('bad-url'), throwsA(isA<HttpException>()));
    • Common built-in exception matchers include throwsArgumentError, throwsStateError, throwsRangeError, throwsFormatException, throwsNoSuchMethodError, and throwsUnsupportedError.

6. Using expectLater

Use await expectLater(...) when testing async behavior to ensure proper sequencing.

// 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!

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).

Related Skills

  • dart-test-fundamentals: Core concepts for structuring tests, lifecycles, and configuration.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.