Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
Add more advice away from predicate (#233)
Browse files Browse the repository at this point in the history
Add a best practice entry and expand the dartdoc for `predicate` to
mention that the failure messages are not as detailed as `TypeMatcher`,
and that the latter is preferred when the value can fail in more than
one way.

Add a generic argument to the example usage - most cases should prefer
to include a generic.

Cleanup an unnecessary private `typedef` with a single usage.
  • Loading branch information
natebosch authored Nov 21, 2023
1 parent 3d03fa1 commit fcbd361
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 6 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,14 @@ why the test failed. For instance compare the failures between
Which: has length of <2>
```

### Prefer TypeMatcher to predicate if the match can fail in multiple ways

The `predicate` utility is a convenient shortcut for testing an arbitrary
(synchronous) property of a value, but it discards context and failures are
opaque. Different failure modes cannot be distinguished in the output which is
determined by a single "description" argument. Using `isA<SomeType>()` and the
`TypeMatcher.having` API to extract and test derived properties in a structured
way brings the context of that structure through to failure messages, so
failures for different reasons will have distinguishable and actionable failure
messages.
21 changes: 15 additions & 6 deletions lib/src/core_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,20 +290,29 @@ class _In<T> extends FeatureMatcher<T> {
description.add('is in ').addDescriptionOf(_source);
}

/// Returns a matcher that uses an arbitrary function that returns
/// true or false for the actual value.
/// Returns a matcher that uses an arbitrary function that returns whether the
/// value is considered a match.
///
/// For example:
///
/// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
/// expect(actual, predicate<num>((v) => (v % 2) == 0, 'is even'));
///
/// Use this method when a value is checked for one conceptual property
/// described by [description].
///
/// If the value can be rejected for more than one reason prefer using [isA] and
/// the [TypeMatcher.having] API to build up a matcher with output that can
/// distinquish between them.
///
/// Using an explicit generict argument allows a passed function literal to have
/// an inferred argument type of [T], and values of the wrong type will be
/// rejected with an informative message.
Matcher predicate<T>(bool Function(T) f,
[String description = 'satisfies function']) =>
_Predicate(f, description);

typedef _PredicateFunction<T> = bool Function(T value);

class _Predicate<T> extends FeatureMatcher<T> {
final _PredicateFunction<T> _matcher;
final bool Function(T) _matcher;
final String _description;

_Predicate(this._matcher, this._description);
Expand Down

0 comments on commit fcbd361

Please sign in to comment.