| |

Dart 49 ๐ŸŽฏ Unit Testing with package:test: Grouping, Assertions, and Expectations

A unit test is a small program that exercises one piece of the code and asserts the expected result. The package:test is the standard testing framework for the Dart, and it provides the test function, the group function, the expect function, and the matchers. The framework runs the tests, collects the results, and reports the pass and the fail. The dart test command is the runner, and the flutter test is the Flutter equivalent. The unit tests are the first line of the defense against the regressions, and the package:test is the tool that makes the tests the readable and the maintainable.

The structure is the main function with the test and the group calls. The test function defines the single test case, and the group function groups the related tests under the shared description. The expect function asserts the actual value against the matcher, and the matcher is the equals, the isA<T>, the throwsA, the contains, the hasLength, and the others. The setUp and the tearDown run the code before and after each test, and the setUpAll and the tearDownAll run it once for the group .

The async support is the built-in. The test function accepts the async callback, and the await works inside the test. The expectLater handles the asynchronous matchers, and the completes, the emits, and the throwsA work with the futures and the streams. The FakeAsync and the fakeAsync handle the timers, and the Clock handles the time-dependent code.

This chapter covers three areas. First, why the unit testing exists โ€” the regression problem and the test-as-documentation. Second, the test structure โ€” the test, the group, the setUp, the tearDown, and the naming. Third, the assertions and the matchers โ€” the expect, the equals, the isA, the throwsA, the contains, the hasLength, the predicate, and the async matchers. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the test lifecycle.

Key point: The package:test provides the test, the group, the expect, the setUp, and the tearDown. The test defines the single test case, the group groups the related tests, and the expect asserts the actual value against the matcher. The setUp and the tearDown run the code before and after each test. The async support is the built-in with the await, and the expectLater handles the asynchronous matchers.


Why the unit testing exists

The regression problem. A change to the code can break the existing behavior. The developer adds the feature, and the unrelated function stops working. The manual testing catches the break sometimes, and the unit tests catch it every time. The test asserts the expected behavior, and the regression fails the test. The suite is the safety net, and the dart test is the runner .

The documentation problem. The test is the executable documentation. The test shows how the function is called, what the inputs are, and what the outputs are. The new developer reads the tests to understand the code. The test that is the clear and the focused is the better documentation than the comment .

The design problem. The code that is hard to test is often the code with the hidden dependencies and the tight coupling. The test forces the dependency injection and the separation. The testable code is the modular and the maintainable. The unit tests improve the design as the side effect .

The refactoring problem. The refactoring is safe when the tests pass. The developer changes the implementation, and the tests verify the behavior is unchanged. The refactoring without the tests is the gamble, and the tests make it the routine .

The package:test and the flutter_test. The package:test is the pure Dart testing framework, and the flutter_test builds on it with the widget testing and the golden testing. The unit tests use the package:test, and the Flutter tests use the flutter_test. The two share the test, the group, the expect, and the matchers .

The trade-off. The tests are the code that must be written and maintained. The test suite adds the time to the development, and the maintenance is the ongoing. The tests that are the brittle and the coupled to the implementation are the liability. The trade-off is between the safety of the tests and the cost of the writing, and the discipline is the focused, the behavior-oriented tests.


a. The test structure

The test file is the Dart file with the .dart extension, and the test package is the import. The main function contains the tests.

import 'package:test/test.dart';

void main() {
  test('the addition', () {
    expect(1 + 1, equals(2));
  });
}

The test function takes the description and the callback. The description is the readable string, and the callback is the test body. The expect asserts the actual value against the matcher .

The group function groups the related tests:

void main() {
  group('the Calculator', () {
    test('adds the numbers', () {
      expect(Calculator.add(1, 2), equals(3));
    });

    test('subtracts the numbers', () {
      expect(Calculator.subtract(5, 3), equals(2));
    });
  });
}

The group takes the description and the callback. The nested groups are the allowed, and the description is the concatenated in the output .

The setUp and the tearDown:

void main() {
  group('the UserRepository', () {
    late UserRepository repository;

    setUp(() {
      repository = UserRepository();
    });

    tearDown(() {
      repository.dispose();
    });

    test('the initial state is the empty', () {
      expect(repository.users, isEmpty);
    });

    test('the add adds the user', () {
      repository.add(User(name: 'Alice'));
      expect(repository.users, hasLength(1));
    });
  });
}

The setUp runs before each test, and the tearDown runs after each test. The late variable is declared in the group scope, and the setUp assigns it. The setUpAll and the tearDownAll run once for the group .

The naming convention. The test description is the behavior, not the method. The “the add adds the user” is the better than the “testAdd”. The description completes the sentence “the test verifies that…”, and the failure message is the readable .

The skip and the timeout:

test('the slow operation', () async {
  // ...
}, skip: 'The known issue');

test('the slow operation', () async {
  // ...
}, timeout: Timeout(Duration(seconds: 30)));

The skip skips the test with the reason, and the timeout overrides the default 30 seconds .


b. The assertions and the matchers

The expect function takes the actual value and the matcher, and it throws the TestFailure when the match fails.

The equality. The equals matches the value equality:

expect(1 + 1, equals(2));
expect('hello', equals('hello'));
expect([1, 2, 3], equals([1, 2, 3]));

The equals uses the == for the primitives and the deep equality for the collections .

The type. The isA<T> matches the type:

expect(value, isA<int>());
expect(value, isA<String>());
expect(value, isA<User>());

The isA<T> passes when the value is the instance of the type. The isNot negates the matcher:

expect(value, isNot(isA<String>()));

The boolean. The isTrue and the isFalse match the boolean:

expect(active, isTrue);
expect(active, isFalse);
expect(value, isNull);
expect(value, isNotNull);

The collections. The contains and the hasLength:

expect([1, 2, 3], contains(2));
expect('hello world', contains('world'));
expect([1, 2, 3], hasLength(3));
expect({'a': 1}, containsPair('a', 1));

The contains matches the element, the hasLength matches the length, and the containsPair matches the key-value pair .

The string. The startsWith, the endsWith, and the matches:

expect('hello world', startsWith('hello'));
expect('hello world', endsWith('world'));
expect('hello world', matches(RegExp(r'^hello')));

The numeric. The greaterThan, the lessThan, and the closeTo:

expect(5, greaterThan(3));
expect(5, lessThan(10));
expect(0.1 + 0.2, closeTo(0.3, 0.0001));

The closeTo handles the floating-point comparison with the tolerance .

The exception. The throwsA matches the exception:

expect(() => int.parse('abc'), throwsA(isA<FormatException>()));
expect(() => throw Exception('boom'), throwsException);
expect(() => throw StateError('bad'), throwsStateError);
expect(() => throw ArgumentError('bad'), throwsArgumentError);

The throwsA takes the matcher for the exception, and the isA<T> matches the type. The throwsException and the throwsStateError are the shorthand .

The predicate. The predicate matches the custom condition:

expect(5, predicate<int>((value) => value.isOdd));
expect(user, predicate<User>((u) => u.name.startsWith('A')));

The predicate takes the function and the optional description .

The anything and the isNot. The anything matches any value, and the isNot negates the matcher:

expect(value, anything);
expect(value, isNot(equals(5)));

The async matchers. The expectLater handles the asynchronous:

test('the async', () async {
  await expectLater(fetchData(), completion(equals('data')));
  await expectLater(fetchError(), throwsA(isA<Exception>()));
  await expectLater(stream, emits(1));
  await expectLater(stream, emitsInOrder([1, 2, 3]));
});

The completion matches the future result, the throwsA matches the future error, the emits matches the stream element, and the emitsInOrder matches the sequence .

The returnsNormally and the isNot. The returnsNormally matches the function that does not throw, and the isNot negates:

expect(() => int.parse('42'), returnsNormally);

c. The patterns and the best practices

The arrange-act-assert. The test is the three parts: the arrange sets the state, the act performs the operation, and the assert verifies the result:

test('the add adds the item', () {
  // The arrange
  final cart = ShoppingCart();

  // The act
  cart.add(Item(name: 'Apple', price: 1.50));

  // The assert
  expect(cart.items, hasLength(1));
  expect(cart.total, equals(1.50));
});

The three parts are the readable, and the structure is the consistent.

The one assertion per concept. The test asserts one concept, and the multiple assertions are the related:

// โœ… The related assertions
test('the create user', () {
  final user = User.create(name: 'Alice');
  expect(user.name, equals('Alice'));
  expect(user.id, isNotNull);
});

// โŒ The unrelated assertions
test('the user and the order', () {
  expect(user.name, equals('Alice'));
  expect(order.total, equals(100));
});

The descriptive names. The test description is the behavior:

test('the empty list has the zero length', () { ... });
test('the add throws when the item is null', () { ... });

The test fixtures. The shared fixtures are in the setUp:

group('the UserService', () {
  late UserService service;
  late MockDatabase db;

  setUp(() {
    db = MockDatabase();
    service = UserService(db);
  });

  test('the find returns the user', () {
    db.seed([User(id: 1, name: 'Alice')]);
    expect(service.find(1)?.name, equals('Alice'));
  });
});

The async tests. The test callback is the async, and the await works:

test('the fetch returns the data', () async {
  final result = await fetchData();
  expect(result, equals('data'));
});

The test organization. The test directory mirrors the lib directory, and the test file has the _test.dart suffix:

lib/
  calculator.dart
  user_service.dart
test/
  calculator_test.dart
  user_service_test.dart

The dart test command discovers the files with the _test.dart suffix in the test directory .


Complete Example Session

// ============================================
// PART 1: THE BASIC TEST
// ============================================

import 'package:test/test.dart';

void main() {
  test('the addition', () {
    expect(1 + 1, equals(2));
  });
}


// ============================================
// PART 2: THE GROUP
// ============================================

void main() {
  group('the Calculator', () {
    test('adds the numbers', () {
      expect(Calculator.add(1, 2), equals(3));
    });

    test('subtracts the numbers', () {
      expect(Calculator.subtract(5, 3), equals(2));
    });

    test('divides the numbers', () {
      expect(Calculator.divide(10, 2), equals(5));
    });
  });
}


// ============================================
// PART 3: THE SETUP AND THE TEARDOWN
// ============================================

void main() {
  group('the UserRepository', () {
    late UserRepository repository;

    setUp(() {
      repository = UserRepository();
    });

    tearDown(() {
      repository.dispose();
    });

    test('the initial state is the empty', () {
      expect(repository.users, isEmpty);
    });

    test('the add adds the user', () {
      repository.add(User(name: 'Alice'));
      expect(repository.users, hasLength(1));
    });

    test('the remove removes the user', () {
      final user = User(name: 'Alice');
      repository.add(user);
      repository.remove(user);
      expect(repository.users, isEmpty);
    });
  });
}


// ============================================
// PART 4: THE SETUPALL AND THE TEARDOWNAALL
// ============================================

void main() {
  group('the DatabaseTest', () {
    late Database db;

    setUpAll(() {
      db = Database.connect();
    });

    tearDownAll(() async {
      await db.close();
    });

    test('the connection is the open', () {
      expect(db.isOpen, isTrue);
    });
  });
}


// ============================================
// PART 5: THE MATCHERS
// ============================================

void main() {
  test('the equality', () {
    expect(1 + 1, equals(2));
    expect([1, 2, 3], equals([1, 2, 3]));
  });

  test('the type', () {
    expect('hello', isA<String>());
    expect(42, isA<int>());
    expect(42, isNot(isA<String>()));
  });

  test('the boolean', () {
    expect(true, isTrue);
    expect(false, isFalse);
    expect(null, isNull);
    expect('value', isNotNull);
  });

  test('the collections', () {
    expect([1, 2, 3], contains(2));
    expect([1, 2, 3], hasLength(3));
    expect({'a': 1}, containsPair('a', 1));
  });

  test('the string', () {
    expect('hello world', startsWith('hello'));
    expect('hello world', endsWith('world'));
    expect('hello world', contains('world'));
    expect('hello world', matches(RegExp(r'^hello')));
  });

  test('the numeric', () {
    expect(5, greaterThan(3));
    expect(5, lessThan(10));
    expect(0.1 + 0.2, closeTo(0.3, 0.0001));
  });
}


// ============================================
// PART 6: THE EXCEPTIONS
// ============================================

void main() {
  test('the exception', () {
    expect(() => int.parse('abc'), throwsA(isA<FormatException>()));
    expect(() => throw Exception('boom'), throwsException);
    expect(() => throw StateError('bad'), throwsStateError);
    expect(() => throw ArgumentError('bad'), throwsArgumentError);
  });

  test('the specific error', () {
    expect(
      () => divide(10, 0),
      throwsA(
        isA<ArgumentError>().having(
          (e) => e.message,
          'the message',
          'The divisor cannot be the zero',
        ),
      ),
    );
  });
}


// ============================================
// PART 7: THE PREDICATE
// ============================================

void main() {
  test('the predicate', () {
    expect(5, predicate<int>((value) => value.isOdd));
    expect(4, predicate<int>((value) => value.isEven));
    expect('Alice', predicate<String>((name) => name.startsWith('A')));
  });
}


// ============================================
// PART 8: THE ASYNC MATCHERS
// ============================================

void main() {
  test('the future completes', () async {
    await expectLater(fetchData(), completion(equals('data')));
  });

  test('the future throws', () async {
    await expectLater(fetchError(), throwsA(isA<Exception>()));
  });

  test('the stream emits', () async {
    await expectLater(countStream(3), emits(1));
  });

  test('the stream emits in order', () async {
    await expectLater(countStream(3), emitsInOrder([1, 2, 3]));
  });
}


// ============================================
// PART 9: THE ARRANGE-ACT-ASSERT
// ============================================

void main() {
  group('the ShoppingCart', () {
    test('the add adds the item', () {
      // The arrange
      final cart = ShoppingCart();

      // The act
      cart.add(Item(name: 'Apple', price: 1.50));

      // The assert
      expect(cart.items, hasLength(1));
      expect(cart.total, equals(1.50));
    });

    test('the total is the sum', () {
      final cart = ShoppingCart()
        ..add(Item(name: 'Apple', price: 1.50))
        ..add(Item(name: 'Bread', price: 2.50));

      expect(cart.total, equals(4.00));
    });
  });
}


// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================

import 'package:test/test.dart';

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});
}

class UserService {
  final List<User> _users = [];
  int _nextId = 1;

  List<User> get users => List.unmodifiable(_users);

  User create(String name, String email) {
    if (name.isEmpty) {
      throw ArgumentError('The name cannot be the empty');
    }
    if (!email.contains('@')) {
      throw ArgumentError('The email is not the valid');
    }

    final user = User(id: _nextId++, name: name, email: email);
    _users.add(user);
    return user;
  }

  User? find(int id) {
    return _users.where((u) => u.id == id).firstOrNull;
  }

  bool remove(int id) {
    final before = _users.length;
    _users.removeWhere((u) => u.id == id);
    return _users.length < before;
  }
}

void main() {
  group('the UserService', () {
    late UserService service;

    setUp(() {
      service = UserService();
    });

    group('the create', () {
      test('the creates the user', () {
        final user = service.create('Alice', 'alice@example.com');

        expect(user.id, equals(1));
        expect(user.name, equals('Alice'));
        expect(user.email, equals('alice@example.com'));
      });

      test('the increments the id', () {
        service.create('Alice', 'alice@example.com');
        final user2 = service.create('Bob', 'bob@example.com');

        expect(user2.id, equals(2));
      });

      test('the adds to the list', () {
        service.create('Alice', 'alice@example.com');

        expect(service.users, hasLength(1));
      });

      test('the throws when the name is the empty', () {
        expect(
          () => service.create('', 'alice@example.com'),
          throwsA(isA<ArgumentError>()),
        );
      });

      test('the throws when the email is the invalid', () {
        expect(
          () => service.create('Alice', 'invalid'),
          throwsA(
            isA<ArgumentError>().having(
              (e) => e.message,
              'the message',
              contains('email'),
            ),
          ),
        );
      });
    });

    group('the find', () {
      test('the returns the user', () {
        final created = service.create('Alice', 'alice@example.com');
        final found = service.find(created.id);

        expect(found, isNotNull);
        expect(found!.name, equals('Alice'));
      });

      test('the returns the null when the missing', () {
        expect(service.find(999), isNull);
      });
    });

    group('the remove', () {
      test('the removes the user', () {
        final user = service.create('Alice', 'alice@example.com');
        final removed = service.remove(user.id);

        expect(removed, isTrue);
        expect(service.users, isEmpty);
      });

      test('the returns the false when the missing', () {
        expect(service.remove(999), isFalse);
      });
    });
  });
}

The ten parts show the basic test, the group, the setUp and the tearDown, the setUpAll and the tearDownAll, the matchers, the exceptions, the predicate, the async matchers, the arrange-act-assert, and the complete example.


Quick Reference

The Test Functions

The functionThe purpose
test(description, callback)The single test
group(description, callback)The group
setUp(callback)Runs before each test
tearDown(callback)Runs after each test
setUpAll(callback)Runs once before the group
tearDownAll(callback)Runs once after the group
expect(actual, matcher)The assertion
expectLater(actual, matcher)The async assertion

The Common Matchers

The matcherThe purpose
equals(value)The equality
isA<T>()The type
isTrue / isFalseThe boolean
isNull / isNotNullThe null
contains(value)The collection contains
hasLength(n)The length
containsPair(k, v)The map pair
startsWith(s)The prefix
endsWith(s)The suffix
matches(regexp)The regex
greaterThan(n)The comparison
lessThan(n)The comparison
closeTo(n, delta)The float
throwsA(matcher)The exception
throwsExceptionThe any exception
returnsNormallyThe no exception
predicate<T>(f)The custom
anythingThe any value
isNot(matcher)The negation

The Async Matchers

The matcherThe purpose
completion(matcher)The future result
throwsA(matcher)The future error
emits(value)The stream element
emitsInOrder([...])The stream sequence
emitsDoneThe stream done
emitsError(matcher)The stream error

The Test Options

The optionThe purpose
skip: 'reason'Skips the test
timeout: Timeout(...)The custom timeout
tags: ['tag']The test tags
retry: 3The retry

The Commands

The commandThe purpose
dart testRuns all the tests
dart test path/to/file.dartRuns the specific file
dart test -n 'the pattern'Runs the matching tests
dart test --coverage=coverageThe coverage
dart test -r expandedThe detailed reporter

Best Practices

โœ… Do This:

// Use the arrange-act-assert
final cart = ShoppingCart();
cart.add(Item(name: 'Apple'));
expect(cart.items, hasLength(1));                                         // โœ…
// Use the descriptive names
test('the empty list has the zero length', () { ... });                   // โœ…
// Use the setUp for the shared fixtures
setUp(() { service = UserService(); });                                   // โœ…
// Use the throwsA for the exceptions
expect(() => int.parse('abc'), throwsA(isA<FormatException>()));          // โœ…
// Use the expectLater for the async
await expectLater(fetchData(), completion(equals('data')));               // โœ…
// Use the group for the related tests
group('the UserService', () { ... });                                     // โœ…

โŒ Don’t Do This:

// Don't test the multiple unrelated concepts
test('the user and the order', () { ... }); // the unrelated              // โŒ
// Don't use the vague names
test('test1', () { ... });  // the meaningless                            // โŒ
// Don't use the setUp for the test-specific state
setUp(() { theTestSpecific = ...; }); // the shared                     // โŒ
// Don't forget the await in the async test
test('the async', () {
  expectLater(fetchData(), completion(equals('data'))); // the missing await // โŒ
});
// Don't use the sleep in the test
await Future.delayed(Duration(seconds: 1));  // the slow and the flaky    // โŒ
// Don't test the implementation detail
expect(service._privateField, equals(5));  // the brittle               // โŒ
// Don't depend on the test order
test('the B', () { expect(state, equals('from A')); }); // the order      // โŒ

Common Pitfalls

The pitfallThe whyThe fix
The flaky testThe timing or the shared stateUse the fakeAsync or the isolated state
The missing awaitThe async testAdd the await
The test order dependencyThe shared stateUse the setUp
The brittle testThe implementation couplingTest the behavior
The slow testThe real I/OUse the mocks
The unclear failureThe vague descriptionUse the descriptive names
The setUp not runningThe scopeCheck the group

Real-World Examples

1. The Basic Test

test('the addition', () => expect(1 + 1, equals(2)));

2. The Group

group('the Calculator', () { test('adds', () { ... }); });

3. The setUp

setUp(() { service = UserService(); });

4. The Exception

expect(() => int.parse('abc'), throwsA(isA<FormatException>()));

5. The Collection

expect([1, 2, 3], contains(2));

6. The Async

await expectLater(fetchData(), completion(equals('data')));

7. The Stream

await expectLater(stream, emitsInOrder([1, 2, 3]));

8. The Predicate

expect(5, predicate<int>((v) => v.isOdd));

9. The Specific Error

expect(() => divide(10, 0), throwsA(isA<ArgumentError>()));

10. The Complete

group('the UserService', () {
  late UserService service;
  setUp(() => service = UserService());
  test('the create', () { ... });
});

Visual

The Test Lifecycle

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TEST LIFECYCLE                                          โ”‚
โ”‚                                                              โ”‚
โ”‚  The group starts:                                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  setUpAll()  โ† runs once                             โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ–ผ                                                         โ”‚
โ”‚  The test 1:                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  setUp()    โ† runs before the test                   โ”‚    โ”‚
โ”‚  โ”‚  the test body                                       โ”‚    โ”‚
โ”‚  โ”‚  tearDown() โ† runs after the test                    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ–ผ                                                         โ”‚
โ”‚  The test 2:                                                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  setUp()                                             โ”‚    โ”‚
โ”‚  โ”‚  the test body                                       โ”‚    โ”‚
โ”‚  โ”‚  tearDown()                                          โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚    โ”‚                                                         โ”‚
โ”‚    โ–ผ                                                         โ”‚
โ”‚  The group ends:                                             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  tearDownAll()  โ† runs once                          โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Arrange-Act-Assert

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE ARRANGE-ACT-ASSERT                                      โ”‚
โ”‚                                                              โ”‚
โ”‚  test('the add adds the item', () {                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  // The ARRANGE                                      โ”‚    โ”‚
โ”‚  โ”‚  final cart = ShoppingCart();                        โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  // The ACT                                           โ”‚    โ”‚
โ”‚  โ”‚  cart.add(Item(name: 'Apple', price: 1.50));         โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  // The ASSERT                                        โ”‚    โ”‚
โ”‚  โ”‚  expect(cart.items, hasLength(1));                   โ”‚    โ”‚
โ”‚  โ”‚  expect(cart.total, equals(1.50));                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚  });                                                         โ”‚
โ”‚                                                              โ”‚
โ”‚  The three parts are the readable and the consistent.        โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Matchers

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE MATCHERS                                                โ”‚
โ”‚                                                              โ”‚
โ”‚  expect(1 + 1, equals(2));          โ† the equality           โ”‚
โ”‚  expect('hello', isA<String>());    โ† the type               โ”‚
โ”‚  expect(true, isTrue);              โ† the boolean            โ”‚
โ”‚  expect(null, isNull);              โ† the null               โ”‚
โ”‚  expect([1, 2, 3], contains(2));    โ† the collection         โ”‚
โ”‚  expect([1, 2, 3], hasLength(3));   โ† the length             โ”‚
โ”‚  expect('hello', startsWith('he')); โ† the prefix             โ”‚
โ”‚  expect(5, greaterThan(3));         โ† the comparison         โ”‚
โ”‚  expect(0.1 + 0.2, closeTo(0.3, 0.001)); โ† the float         โ”‚
โ”‚                                                              โ”‚
โ”‚  expect(() => int.parse('abc'),     โ† the exception          โ”‚
โ”‚    throwsA(isA<FormatException>()));                         โ”‚
โ”‚                                                              โ”‚
โ”‚  await expectLater(fetchData(),     โ† the async              โ”‚
โ”‚    completion(equals('data')));                              โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Test Output

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TEST OUTPUT                                             โ”‚
โ”‚                                                              โ”‚
โ”‚  The passing:                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  00:01 +3: the UserService                          โ”‚    โ”‚
โ”‚  โ”‚    the create                                        โ”‚    โ”‚
โ”‚  โ”‚      the creates the user                            โ”‚    โ”‚
โ”‚  โ”‚      the increments the id                           โ”‚    โ”‚
โ”‚  โ”‚      the adds to the list                            โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  All tests passed!                                   โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ”‚  The failing:                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚  00:01 +2 -1: the UserService                       โ”‚    โ”‚
โ”‚  โ”‚    the create                                        โ”‚    โ”‚
โ”‚  โ”‚      the creates the user                            โ”‚    โ”‚
โ”‚  โ”‚      the increments the id                           โ”‚    โ”‚
โ”‚  โ”‚      โœ— the adds to the list                          โ”‚    โ”‚
โ”‚  โ”‚        Expected: the length of 1                     โ”‚    โ”‚
โ”‚  โ”‚          Actual: the length of 0                     โ”‚    โ”‚
โ”‚  โ”‚                                                       โ”‚    โ”‚
โ”‚  โ”‚  Some tests failed.                                  โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
package:testThe Dart testing framework
testThe single test
groupThe group of the related tests
setUp / tearDownThe per-test setup
setUpAll / tearDownAllThe per-group setup
expectThe assertion
expectLaterThe async assertion
equalsThe equality matcher
isA<T>The type matcher
throwsAThe exception matcher
completionThe async matcher
emitsInOrderThe stream matcher

Key takeaways:

  • The package:test is the standard Dart testing framework. The test defines the single test, the group groups the related tests, and the expect asserts the actual value against the matcher. The setUp and the tearDown run the code before and after each test .
  • The expect takes the actual value and the matcher. The equals matches the equality, the isA<T> matches the type, the contains matches the collection, the hasLength matches the length, the throwsA matches the exception, and the predicate matches the custom condition .
  • The async support is the built-in. The test callback is the async, the await works, and the expectLater handles the asynchronous matchers. The completion matches the future, the throwsA matches the future error, and the emitsInOrder matches the stream sequence .
  • The setUp and the tearDown run the per-test code. The setUpAll and the tearDownAll run the per-group code. The late variable is declared in the group scope, and the setUp assigns it .
  • The arrange-act-assert is the test structure. The arrange sets the state, the act performs the operation, and the assert verifies the result. The three parts are the readable and the consistent .
  • The descriptive names are the documentation. The test description is the behavior, and the failure message is the readable. The “the add adds the item” is the better than the “testAdd” .
  • The test files mirror the lib structure. The test directory contains the _test.dart files, and the dart test command discovers them. The dart test path/to/file.dart runs the specific file, and the -n runs the matching tests .
  • The tests are the safety net. The test catches the regression, the test documents the behavior, and the test enables the refactoring. The result is the confident development, which is the foundation of the reliable code.

Remember: The package:test is the standard Dart testing framework. The test defines the tests, the group groups them, and the expect asserts the values. The setUp and the tearDown manage the fixtures, and the expectLater handles the async. The matchers โ€” the equals, the isA, the throwsA, the contains, the hasLength, the predicate, the completion, the emitsInOrder โ€” are the assertions. The result is the tested, the documented, and the maintainable code, which is the foundation of the professional development.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!