matcher

v0.12.17

Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.

Package archive: https://pubdev.letsnova.ru/api/archives/matcher/0.12.17.tar.gz

Installdart pub add matcher

Readme

pub package package publisher

Support for specifying test expectations, such as for unit tests.

The matcher library provides a third-generation assertion mechanism, drawing inspiration from Hamcrest.

For more information on testing, see Unit Testing with Dart.

Using matcher

Expectations start with a call to expect() or expectAsync().

Any matchers package can be used with expect() to do complex validations:

import 'package:test/test.dart';
                
                void main() {
                  test('.split() splits the string on the delimiter', () {
                    expect('foo,bar,baz', allOf([
                      contains('foo'),
                      isNot(startsWith('bar')),
                      endsWith('baz')
                    ]));
                  });
                }
                

If a non-matcher value is passed, it will be wrapped with equals().

Exception matchers

You can also test exceptions with the throwsA() function or a matcher such as throwsFormatException:

import 'package:test/test.dart';
                
                void main() {
                  test('.parse() fails on invalid input', () {
                    expect(() => int.parse('X'), throwsFormatException);
                  });
                }
                

Future Matchers

There are a number of useful functions and matchers for more advanced asynchrony. The completion() matcher can be used to test Futures; it ensures that the test doesn't finish until the Future completes, and runs a matcher against that Future's value.

import 'dart:async';
                
                import 'package:test/test.dart';
                
                void main() {
                  test('Future.value() returns the value', () {
                    expect(Future.value(10), completion(equals(10)));
                  });
                }
                

The throwsA() matcher and the various throwsExceptionType matchers work with both synchronous callbacks and asynchronous Futures. They ensure that a particular type of exception is thrown:

import 'dart:async';
                
                import 'package:test/test.dart';
                
                void main() {
                  test('Future.error() throws the error', () {
                    expect(Future.error('oh no'), throwsA(equals('oh no')));
                    expect(Future.error(StateError('bad state')), throwsStateError);
                  });
                }
                

The expectAsync() function wraps another function and has two jobs. First, it asserts that the wrapped function is called a certain number of times, and will cause the test to fail if it's called too often; second, it keeps the test from finishing until the function is called the requisite number of times.

import 'dart:async';
                
                import 'package:test/test.dart';
                
                void main() {
                  test('Stream.fromIterable() emits the values in the iterable', () {
                    var stream = Stream.fromIterable([1, 2, 3]);
                
                    stream.listen(expectAsync1((number) {
                      expect(number, inInclusiveRange(1, 3));
                    }, count: 3));
                  });
                }
                

Stream Matchers

The test package provides a suite of powerful matchers for dealing with asynchronous streams. They're expressive and composable, and make it easy to write complex expectations about the values emitted by a stream. For example:

import 'dart:async';
                
                import 'package:test/test.dart';
                
                void main() {
                  test('process emits status messages', () {
                    // Dummy data to mimic something that might be emitted by a process.
                    var stdoutLines = Stream.fromIterable([
                      'Ready.',
                      'Loading took 150ms.',
                      'Succeeded!'
                    ]);
                
                    expect(stdoutLines, emitsInOrder([
                      // Values match individual events.
                      'Ready.',
                
                      // Matchers also run against individual events.
                      startsWith('Loading took'),
                
                      // Stream matchers can be nested. This asserts that one of two events are
                      // emitted after the "Loading took" line.
                      emitsAnyOf(['Succeeded!', 'Failed!']),
                
                      // By default, more events are allowed after the matcher finishes
                      // matching. This asserts instead that the stream emits a done event and
                      // nothing else.
                      emitsDone
                    ]));
                  });
                }
                

A stream matcher can also match the async package's StreamQueue class, which allows events to be requested from a stream rather than pushed to the consumer. The matcher will consume the matched events, but leave the rest of the queue alone so that it can still be used by the test, unlike a normal Stream which can only have one subscriber. For example:

import 'dart:async';
                
                import 'package:async/async.dart';
                import 'package:test/test.dart';
                
                void main() {
                  test('process emits a WebSocket URL', () async {
                    // Wrap the Stream in a StreamQueue so that we can request events.
                    var stdout = StreamQueue(Stream.fromIterable([
                      'WebSocket URL:',
                      'ws://localhost:1234/',
                      'Waiting for connection...'
                    ]));
                
                    // Ignore lines from the process until it's about to emit the URL.
                    await expectLater(stdout, emitsThrough('WebSocket URL:'));
                
                    // Parse the next line as a URL.
                    var url = Uri.parse(await stdout.next);
                    expect(url.host, equals('localhost'));
                
                    // You can match against the same StreamQueue multiple times.
                    await expectLater(stdout, emits('Waiting for connection...'));
                  });
                }
                

The following built-in stream matchers are available:

  • emits() matches a single data event.
  • emitsError() matches a single error event.
  • emitsDone matches a single done event.
  • mayEmit() consumes events if they match an inner matcher, without requiring them to match.
  • mayEmitMultiple() works like mayEmit(), but it matches events against the matcher as many times as possible.
  • emitsAnyOf() consumes events matching one (or more) of several possible matchers.
  • emitsInOrder() consumes events matching multiple matchers in a row.
  • emitsInAnyOrder() works like emitsInOrder(), but it allows the matchers to match in any order.
  • neverEmits() matches a stream that finishes without matching an inner matcher.

You can also define your own custom stream matchers with StreamMatcher().

Best Practices

Prefer semantically meaningful matchers to comparing derived values

Matchers which have knowledge of the semantics that are tested are able to emit more meaningful messages which don't require reading test source to understand why the test failed. For instance compare the failures between expect(someList.length, 1), and expect(someList, hasLength(1)):

// expect(someList.length, 1);
                  Expected: <1>
                    Actual: <2>
                
// expect(someList, hasLength(1));
                  Expected: an object with length of <1>
                    Actual: ['expected value', 'unexpected value']
                     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.

Changelog

0.12.17

  • Require Dart 3.4
  • Move to dart-lang/test monorepo.

0.12.16+1

  • Require Dart 3.0
  • Support latest version of package:test_api.

0.12.16

  • Expand bounds on test_api dependency to allow the next breaking release which will remove the cyclic dependency on this package.

0.12.15

  • Add package:matcher/expect.dart library. Copies the implementation of expect and the asynchronous matchers from package:test.

0.12.14

  • Add containsOnce matcher.
  • Deprecate isCyclicInitializationError and NullThrownError. These errors will be removed from the SDK. Update them to catch more general errors.

0.12.13

  • Require Dart 2.17 or greater.
  • Make isCastError no longer depend on the deprecated CastError type.
  • Annotate TypeMatcher.having with useResult.

0.12.12

  • Add a best practices section to readme.
  • Populate the pubspec repository field.

0.12.11

  • Change many argument types from dynamic to Object?.
  • Fix stringContainsInOrder to account for repetitions and empty strings.
    • Note: This may break some existing tests, as the behavior does change.

0.12.10

  • Stable release for null safety.

0.12.10-nullsafety.3

  • Update SDK constraints to >=2.12.0-0 <3.0.0 based on beta release guidelines.

0.12.10-nullsafety.2

  • Allow prerelease versions of the 2.12 sdk.

0.12.10-nullsafety.1

  • Allow 2.10 stable and 2.11.0 dev SDK versions.

0.12.10-nullsafety

  • Migrate to NNBD.
    • Apis have been updated to express intent of the existing code and how it handled nulls.

0.12.9

  • Improve mismatch descriptions for deep matches. Previously, if the user tried to do a deep match where the expectation included a complex matcher (such as a "having" matcher), the failure message would just say "failed to match ..."; it wouldn't call on the expectation's matcher to explain why the match failed.

0.12.8

  • Add a mismatch description to TypeMatcher.

0.12.7

  • Deprecate the mirror_matchers.dart library.

0.12.6

  • Update minimum Dart SDK to 2.2.0.
  • Consistently point to isA as a replacement for instanceOf.
  • Pretty print with private type names.

0.12.5

  • Add isA() to create TypeMatcher instances in a more fluent way.
  • Potentially breaking bug fix. Ordering matchers no longer treat objects with a partial ordering (such as NaN for double values) as if they had a complete ordering. For instance greaterThan now compares with the > operator rather not < and not =. This could cause tests which relied on this bug to start failing.

0.12.4

  • Add isCastError.

0.12.3+1

  • Set max SDK version to <3.0.0, and adjusted other dependencies.

0.12.3

  • Many improvements to TypeMatcher

    • Can now be used directly as const TypeMatcher<MyType>().

    • Added a type parameter to specify the target Type.

      • Made the name constructor parameter optional and marked it deprecated. It's redundant to the type parameter.
    • Migrated all isType matchers to TypeMatcher.

    • Added a having function that allows chained validations of specific features of the target type.

      /// Validates that the object is a [RangeError] with a message containing
                      /// the string 'details' and `start` and `end` properties that are `null`.
                      final _rangeMatcher = isRangeError
                         .having((e) => e.message, 'message', contains('details'))
                         .having((e) => e.start, 'start', isNull)
                         .having((e) => e.end, 'end', isNull);
                      
  • Deprecated the isInstanceOf class. Use TypeMatcher instead.

  • Improved the output of Matcher instances that fail due to type errors.

0.12.2+1

  • Updated SDK version to 2.0.0-dev.17.0

0.12.2

  • Fixed unorderedMatches in cases where the matchers may match more than one element and order of the elements doesn't line up with the order of the matchers.

  • Add containsAll matcher for Iterables. This Matcher checks that all values/matchers in an expected iterable are satisfied by an element in the value without allowing the same value to satisfy multiple matchers.

0.12.1+4

  • Fixed SDK constraint to allow edge builds.

0.12.1+3

  • Make predicate and pairwiseCompare generic methods to allow typed functions to be passed to them as arguments.

  • Make internal implementations take better advantage of type promotion to avoid dynamic call overhead.

0.12.1+2

  • Fixed small documentation issues.

  • Fixed small issue in StringEqualsMatcher.

  • Update to support future Dart language changes.

0.12.1+1

  • Produce a better error message when a CustomMatcher's feature throws.

0.12.1

  • Add containsAllInOrder matcher for Iterables

0.12.0+2

  • Fix all strong-mode warnings.

0.12.0+1

  • Fix test files to use test instead of unittest pkg.

0.12.0

  • Moved a number of members to the unittest package.

    • TestFailure, ErrorFormatter, expect, fail, and 'wrapAsync'.
    • completes, completion, throws, and throwsA Matchers.
    • The Throws class.
    • All of the throws...Error Matchers.
  • Removed FailureHandler, DefaultFailureHandler, configureExpectFailureHandler, and getOrCreateExpectFailureHandler. Now that expect is in the unittest package, these are no longer needed.

  • Removed the name parameter for isInstanceOf. This was previously deprecated, and is no longer necessary since all language implementations now support converting the type parameter to a string directly.

0.11.4+6

  • Fix a bug introduced in 0.11.4+5 in which operator matchers broke when taking lists of matchers.

0.11.4+5

  • Fix all strong-mode warnings.

0.11.4+4

  • Deprecate the name parameter to isInstanceOf. All language implementations now support converting the type parameter to a string directly.

0.11.4+3

  • Fix the examples for equalsIgnoringWhitespace.

0.11.4+2

  • Improve the formatting of strings that contain unprintable ASCII characters.

0.11.4+1

  • Correctly match and print Strings containing characters that must be represented as escape sequences.

0.11.4

  • Remove the type checks in the isEmpty and isNotEmpty matchers and simply access the isEmpty respectively isNotEmpty fields. This allows them to work with custom collections. See Issue 21792 and Issue 21562

0.11.3+1

  • Fix the prints matcher test on dart2js.

0.11.3

  • Add a prints matcher that matches output a callback emits via print.

0.11.2

  • Add an isNotEmpty matcher.

0.11.1+1

  • Refactored libraries and tests.

  • Fixed spelling mistake.

0.11.1

  • Added isNaN and isNotNaN matchers.

0.11.0

  • Removed deprecated matchers.

0.10.1+1

  • Get the tests passing when run on dart2js in minified mode.

0.10.1

  • Compare sets order-independently when using equals().

0.10.0+3

  • Removed @deprecated annotation on matchers due to Issue 19173

0.10.0+2

  • Added types to a number of constants.

0.10.0+1

  • Matchers related to bad language use have been removed. These represent code structure that should rarely or never be validated in tests.

    • isAbstractClassInstantiationError
    • throwsAbstractClassInstantiationError
    • isFallThroughError
    • throwsFallThroughError
  • Added types to a number of method arguments.

  • The structure of the library and test code has been updated.