Skip to content

prefer_test_matchers

v0.4.0 Warning Testing Rules

Flags an expect() or expectLater() call whose second argument is a plain value rather than a Matcher.

expect(x, 1) passes and fails correctly, but when it fails all it can say is Expected: <1> Actual: <2>. A matcher describes the assertion, so the failure names it.

Each of these asserts the right thing and reports it badly:

void main() {
test('keeps three scores', () {
final scores = [7, 8, 9];
expect(scores.length, 3);
expect(scores, [7, 8, 9]);
expect(scores.isEmpty, false);
});
}

expect(scores.length, 3) failing prints Expected: <3> Actual: <2> — the count, not the list. You then go and print the list by hand.

The same three assertions, said with matchers:

void main() {
test('keeps three scores', () {
final scores = [7, 8, 9];
expect(scores, hasLength(3));
expect(scores, equals([7, 8, 9]));
expect(scores, isNotEmpty);
});
}

Now the first one failing prints Expected: an object with length of <3> Actual: [7, 8] Which: has length of <2>.

Instead of Write
expect(value, 'hello') expect(value, equals('hello'))
expect(flag, true) expect(flag, isTrue)
expect(flag, false) expect(flag, isFalse)
expect(result, null) expect(result, isNull)
expect(items.length, 3) expect(items, hasLength(3))
expect(items, [1, 2]) expect(items, equals([1, 2]))
void main() {
test('parses a greeting', () {
final value = 'hello';
final result = null;
final flag = true;
expect(value, equals('hello'));
expect(flag, isTrue);
expect(result, isNull);
});
}

The rule reads the second argument of both functions:

void main() {
test('resolves to three scores', () async {
// Don't
await expectLater(loadScores(), completion([7, 8, 9]));
// Do
await expectLater(loadScores(), completion(equals([7, 8, 9])));
});
}
Future<List<int>> loadScores() async => [7, 8, 9];

A Matcher in any form satisfies the rule — including one held in a variable, and isA<T>():

void main() {
test('accepts a matcher from a variable', () {
final expected = equals(42);
expect(1 + 41, expected);
expect('hello', isA<String>());
});
}

A reason: named argument is ignored, since the rule only reads the second positional argument.

See also: test package - Matchers

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
prefer_test_matchers: true

To turn it off again:

many_lints.yaml
rules:
prefer_test_matchers: false

To keep the rule on but skip certain paths, use per-rule exclude.