Assertions in Dart and Flutter tests: universal and custom matchers
This is the part of the ultimate cheat sheet dedicated to:
- universal matcher
predicate
, - custom matcher.
In this series:
- expect and matcher
- equality matchers
- type and error matchers
- collection matchers
- numeric and comparable matchers
- universal and custom matchers
- matcher operators
- asynchronous expect and matchers
- Flutter widget matchers
- accessibility matchers
- golden matchers
- mock invocations and parameters
Universal matcher
Generally speaking, most types of checks a developer might ever need to perform in expect()
methods can be expressed with a single matcher - predicate
. It accepts a predicate - a Function
with one parameter that returns bool
, where you can decide if the parameter matches your expectations. For example:
test('expect: predicate β
', () {
final result = Result(0);
expect(result, predicate((e) => e is Result && e.value == 0));
expect(result, predicate<Result>((result) => result.value == 0));
});
Depending on the type of required check, predicate
might be exactly the matcher you need. But there is a bunch of more focused matchers which provide more readable code and output. Letβs compare.
A test with a predicate
matcher:
test('expect: predicate β', () {
final result = 1;
expect(result, predicate((e) => e == 0));
});
gives the following output:
Expected: satisfies function
Actual: <1>
It can be improved with predicate
matcher description
parameter. This test:
test('expect: predicate β', () {
final result = 1;
expect(result, predicate((e) => e == 0, 'Result should be 0!'));
});
prints:
Expected: Result should be 0!
Actual: <1>
While a test with an equals
matcher:
test('expect: equals β', () {
final result = 1;
expect(result, equals(0));
});
gives more information about the expected result with less code:
Expected: <0>
Actual: <1>
Always prefer using focused matchers when available.
Custom matchers
If you did not find a matcher that satisfies your requirements, you can create your own matcher.
For example, letβs create a matcher that validates the value
field. For that, we need a child of CustomMatcher
class:
class HasValue extends CustomMatcher {
HasValue(Object? valueOrMatcher)
: super(
'an object with value field of',
'value field',
valueOrMatcher,
);
@override
Object? featureValueOf(dynamic actual) => actual.value;
}
The HasValue
class extends CustomMatcher
and accepts one parameter, which can be a value or another matcher. It calls the parent constructor with the feature name and description, which will be used in the output if the test fails.
It also overrides the featureValueOf
method that attempts to get value
property of the actual
object passed to expect()
. It is supposed to work with any type that declares the value
property, like the Result
class created in the expect and matchers part. In case actual
does not declare such a property, our featureValueOf
implementation will throw, but the base CustomMatcher
class calls it inside try
/ catch
bloc and will fail the test gracefully.
To be consistent with common practices of declaring a matcher, letβs also declare a factory method to create our matcher:
Matcher hasValue(Object? valueOrMatcher) => HasValue(valueOrMatcher);
Now it can be used in any of these ways:
test('expect: hasValue β
', () {
final result = Result(0);
expect(result, hasValue(0));
expect(result, HasValue(0));
expect(result, hasValue(equals(0)));
});
Notice that hasValue
matcher can accept both 0
and equals(0)
matcher. In fact, it can accept any other matcher:
test('expect: hasValue β
', () {
final result = Result(0);
expect(result, hasValue(isZero));
expect(result, hasValue(lessThan(1)));
});
In case of a failing test:
test('expect: hasValue β', () {
final result = Result(1);
expect(result, hasValue(0));
});
The output contains the feature name and description passed to CustomMatcher
constructor:
Expected: an object with value property of <0>
Actual: Result:<Result{result: 1}>
Which: has value property with value <1>
Originally published at Invertase blog. Check out their awesome Authors Program!
Hi! ππ» Iβm Anna, Google Developer Expert in Flutter from Ukraine πΊπ¦ Follow me on Twitter, GitHub, YouTube, Medium to get notifications about my latest work.
Itβs early 2023, and we in Ukraine are still fighting against russians committing genocide on our lands. If you find this content useful and have a coin to spare, support us with your donations. Stand with Ukraine!