In Dart's package:test, how do test, group, setUp, tearDown and setUpAll organise a suite, and in what order do the hooks run?
answer
- group prefixes the test name
- setUp runs before every test
- tearDown runs even after a failure
- outer setUp first, inner tearDown first
- setUpAll once, easy to leak state
basics
~20 stest declares one case; group nests cases under a name prefix. setUp runs before each test, outer groups first; tearDown runs after each test, even a failing one, inner groups first; setUpAll and tearDownAll run once per group.
solid answer
~50 s`test('description', body)` registers one case; the body may be `async`. `group('DateParser', () { ... })` nests tests, and its description is prepended to theirs, so reports read `DateParser parses ISO dates`. `setUp` runs before **each** test in the group where it is declared, after any `setUp` in parent groups, and callbacks in one group run in declaration order. `tearDown` runs after each test **even if it failed**, before parent-group tear-downs, and in reverse declaration order. `setUpAll`/`tearDownAll` run once around all tests of a group and are skipped if none of its tests run; the package's own docs warn they easily create hidden dependencies between tests. `addTearDown` registers cleanup from inside a running test, runs before the `tearDown` callbacks, and fits resources created mid-test. Files named `*_test.dart` under `test/` are what `dart test` picks up by default.
code
dart · 18 linesimport 'package:test/test.dart';
void main() {
final log = <String>[];
setUp(() => log.add('top setUp'));
tearDown(() => log.add('top tearDown'));
group('outer', () {
setUp(() => log.add('outer setUp'));
tearDown(() => log.add('outer tearDown'));
test('case', () {
addTearDown(() => log.add('addTearDown'));
log.add('body');
});
});
// log: top setUp, outer setUp, body, addTearDown, outer tearDown, top tearDown
}go deeper
Recall test, group, expect and the four hooks, and that tearDown still runs when a test fails.
Explain the exact order across nested groups, where addTearDown fits, and why setUpAll is skipped when no test in the group runs.
Show you keep tests independent: fresh fixtures in setUp, shared ones only when read-only and costly, and shuffled order in CI to catch coupling.
Weigh suite speed from shared fixtures against the debugging cost of order-dependent failures across a large codebase.
## The building blocks `package:test` is the standard Dart test framework, used directly for Dart packages and underneath `flutter_test` for Flutter. A test file is an ordinary Dart program whose `main()` **declares** tests; the runner then executes them. - **`test(description, body)`** declares one test case. `body` can return a `Future`; the runner waits for it. - **`group(description, body)`** nests tests. The group's description is prepended to every test inside it, and hooks declared inside apply only to that group. - **`expect(actual, matcher)`** asserts; a plain value as `matcher` is wrapped in `equals`. ```dart import 'package:test/test.dart'; import 'package:dates/dates.dart'; void main() { group('IsoDateParser', () { late IsoDateParser parser; setUp(() => parser = IsoDateParser()); test('parses a calendar date', () { expect(parser.parse('2026-09-29'), equals(DateTime(2026, 9, 29))); }); test('keeps the UTC flag for a Z suffix', () { expect(parser.parse('2026-09-29T10:00:00Z').isUtc, isTrue); }); }); } ``` The report shows `IsoDateParser parses a calendar date`, which is why group names are nouns and test names read as behaviour. ## The four hooks and their order | Hook | Runs | Order among several | |---|---|---| | `setUp` | before each test in its group | parent groups first, then declaration order | | `tearDown` | after each test, **even if it failed** | own group before parents, reverse declaration order | | `setUpAll` | once before the group's first test | parent groups first | | `tearDownAll` | once after the group's last test | own group before parents | For a test nested two groups deep, the sequence is: 1. top-level `setUp` callbacks; 2. outer group `setUp`; 3. inner group `setUp`; 4. the test body; 5. any `addTearDown` callbacks, most recent first; 6. inner group `tearDown`; 7. outer group `tearDown`; 8. top-level `tearDown`. `setUpAll` and `tearDownAll` wrap the whole group once and **do not run if none of the group's tests run**, for example when a name filter excludes them all. ## addTearDown `addTearDown(callback)` is called **inside** a running test (or a helper it calls). It registers cleanup for that test only, runs **before** the `tearDown` callbacks, and follows last-in, first-out order. It is the tidy choice when a helper creates a resource halfway through a test: ```dart // needs import 'dart:io'; test('reads dates from a temp file', () async { final dir = await Directory.systemTemp.createTemp(); addTearDown(() => dir.delete(recursive: true)); // ... }); ``` Calling it outside a test throws a `StateError`. Called from `setUpAll` or `tearDownAll`, it runs after all tests in the suite instead. ## Why setUp beats setUpAll by default `setUp` gives each test a **fresh** fixture, so tests cannot affect each other and can run in any order. `setUpAll` shares one fixture across a group, which is faster for expensive resources (a database file, a spawned process) but lets one test's mutation leak into the next. package:test's own documentation recommends `setUp` and reserves `setUpAll` for callbacks that are prohibitively slow. Shared state of this kind is what the runner's `--test-randomize-ordering-seed` option, which shuffles test order, is good at exposing. ## Where tests live - The `test` package is a **dev dependency**. - Files end in `_test.dart` and live under `test/`; `dart test` with no path finds them recursively. - Run one file with `dart test test/iso_date_parser_test.dart`. ## Mistakes interviewers look for - Declaring shared state as a top-level `final` initialised once, instead of resetting it in `setUp`. - Assuming `tearDown` is skipped when a test fails, and putting cleanup at the end of the test body instead. - Nesting groups only for indentation, while hooks in them unexpectedly apply to every test inside. ## A worked layout for a date library A test file for a date-parsing library usually mirrors the public API: 1. a top-level `group('IsoDateParser', ...)` with a `setUp` that creates a fresh parser; 2. nested groups per behaviour, such as `group('calendar dates', ...)` and `group('rejects', ...)`, so failures read `IsoDateParser rejects month 13`; 3. a `setUpAll` only for something expensive and read-only, such as loading a large table of time-zone rules from disk; 4. `addTearDown` inside the few tests that create temporary files. Keeping hooks close to the tests that need them means a reader sees the whole context of a failing test within one screen, and `dart test -n "IsoDateParser rejects"` runs exactly one behaviour. ## Where package:test stops The framework gives structure, assertions and a runner. Choosing what a unit is, how to name behaviour, and how to arrange a test body are general testing questions; in Flutter projects, `testWidgets` adds a widget tester on top of the same `group`, `setUp` and `expect`.
- When is setUpAll the right choice despite the warning in package:test's docs?When creating the fixture is genuinely expensive and tests only read it: starting a local server, compiling a large asset, opening a read-only database. If any test mutates the fixture, move to `setUp` or reset the mutated part per test, otherwise test order starts to matter.
- How does addTearDown differ from tearDown?`tearDown` is declared at group level and runs after every test in that group. `addTearDown` is called during one running test, cleans up only for it, and runs before the group's `tearDown` callbacks, most recent first. It suits helpers that create resources on demand.
saying these in an interview costs you the question
- Believes tearDown is skipped when the test body throws
- Thinks inner-group setUp runs before the outer group's setUp
- Uses setUpAll for mutable fixtures and wonders why order matters
- Calls addTearDown at the top level of main
- Thinks group only changes indentation, not hook scope