Assertions in Dart and Flutter tests: equality matchers

This is the part of the ultimate cheat sheet dedicated to:

  • matcher equals,
  • equality matchers.

In this series:

Matcher equals

In the example below, we pass 0 as a matcher parameter:

test('expect: value βœ…', () {
final result = 0;
expect(result, 0);
});

In such a case, when the value is passed, an equals matcher is used implicitly. It is equivalent to:

test('matcher: equals βœ…', () {
final result = 0;
expect(result, equals(0));
});

It is probably the most commonly used matcher, explicitly or implicitly.

The equals matcher uses the equality operator to perform the comparison. By default, classes in Dart are compared β€œby reference” and not β€œby value”. Thus, if applied to custom objects like this Result class here:

class Result {
Result(this.value);

final int value;
}

equals matcher fails this test:

test('expect: equals ❌', () {
final result = Result(0);
expect(result, equals(Result(0)));
});

with the following output:

Expected: <Instance of 'Result'>
Actual: <Instance of 'Result'>

It is a good idea to override the .toString() method to make the output more meaningful. For this improved Result class implementation:

class Result {
Result(this.value);

final int value;

@override
String toString() => 'Result{value: $value}';
}

the test output changes to:

Expected: Result:<Result{value: 0}>
Actual: Result:<Result{value: 0}>

To make it pass, the Result class has to override the operator ==, for example like this:

class Result {
Result(this.value);

final int value;

@override
String toString() => 'Result{value: $value}';

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Result &&
runtimeType == other.runtimeType &&
value == other.value;

@override
int get hashCode => value.hashCode;
}

Equality matchers

same

The same matcher makes sure expected and actual results are exactly the same instance. This test:

test('expect: same ❌', () {
final result = Result(1);
expect(result, same(Result(1)));
});

fails with the following output:

Expected: same instance as Result:<Result{result: 1}>
Actual: Result:<Result{result: 1}>

But this test passes:

test('expect: same βœ…', () {
final result = Result(1);
expect(result, same(result));
});

Interesting observation regarding const. This test also passes:

test('expect: same βœ…', () {
final result = 1;
expect(result, same(1));
});

because 1 is a const and only one instance exists in memory. The same applies when custom classes declare const constructors and instances are created with const modifier. If the Result class is updated to declare a const constructor:

class Result {
const Result(this.value);

final int value;
}

this test also passes:

test('expect: same βœ…', () {
final result = const Result(1);
expect(result, same(const Result(1)));
});

But this test still fails:

test('expect: same ❌', () {
final result = Result(1);
expect(result, same(Result(1)));
});

because without using const, two different instances of Result are created.

null matchers

The next pair of matchers is quite simple: isNull and isNotNull check result nullability.

test('expect: isNull ❌', () {
final result = 0;
expect(result, isNull);
});

fails with:

Expected: null
Actual: <0>

And this test passes:

test('expect: isNotNull βœ…', () {
final result = 0;
expect(result, isNotNull);
});

bool matchers

The next pair of equality matchers is self-explanatory: isTrue and isFalse. These tests pass:

test('expect: isTrue βœ…', () {
final result = 0;
expect(result < 1, isTrue);
});
test('expect: isFalse βœ…', () {
final result = 0;
expect(result > 1, isFalse);
});

anything

The anything matcher matches literally any value. It is used in any from mockito package or any<T>() from mocktail, which we’ll discuss in another part. However, it’s not a commonly used matcher in client application tests.

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!

--

--

Anna Leushchenko πŸ‘©β€πŸ’»πŸ’™πŸ“±πŸ‡ΊπŸ‡¦

Google Developer Expert in Dart and Flutter | Author, speaker at tech events, mentor, OSS contributor | Passionate mobile apps creator