A catalog of unit testing patterns for Flutter and Dart, structured in the spirit of the Gang of Four Design Patterns.
Introduction → · Catalog Index ↓ · Glossary →
Every entry is a self-contained chapter with prose and runnable Dart tests. Pick a pattern below, or read the introduction first.
Click any chapter to open its pattern — prose, production code, and tests.
| # | Chapter |
|---|---|
| — | Introduction: The Message Metaphor |
| # | Chapter | Subject |
|---|---|---|
| 1 | Receiving a Message and Responding | MessageFormatter — relative-time labels, message previews |
| 2 | Changing State | UnreadCounter — badge on a conversation row |
| 3 | Sending Out More Messages | ChatManager.sendMessage calling analytics |
| 4 | Side Effects — Completeness of Outgoing Calls | ChatManager.sendMessage — persist, observe, log |
| # | Chapter | Subject |
|---|---|---|
| 5 | Exceptions & Error Handling | Nine error-handling patterns (E1–E9) on ChatManager |
| — | ↳ E1: Exception Throwing | |
| — | ↳ E2: Exception Types | |
| — | ↳ E3: Error Messages | |
| — | ↳ E4: Exception Handling | |
| — | ↳ E5: State After Exception | |
| — | ↳ E6: Fallback Mechanisms | |
| — | ↳ E7: Input Validation | |
| — | ↳ E8: Boundary Conditions | |
| — | ↳ E9: Resource Cleanup | |
| 6 | Adversarial Inputs | ChatManager.receiveMessage — out-of-order timestamps |
| 7 | Initial State and Setup | ChatManager loading message history |
| 8 | Concurrency and Timing | Concurrent sends + delivery receipts |
| 9 | Idempotence | Dedup by stable clientMessageId |
| # | Chapter | Subject |
|---|---|---|
| 10 | Memory and Resource Management | IncomingMessageBinder — stream subscriptions |
| 11 | Third-Party Integration | ChatService wrapping a backend HTTP client |
| 12 | Fallbacks and Redundancies | MessageRepository — remote + cache + placeholder |
| 13 | Performance and Timing | TypingIndicator — debounced "stopped typing" |
| 14 | Security and Input Validation | MessageSanitizer — HTML/null-byte stripping |
| 15 | State Transitions | MessageDelivery lifecycle |
All chapters share the same imagined chat app as their running example, so the catalog reads as one story. Each chapter's code/ folder is self-contained — no cross-chapter imports required.
Every method call is a message. An object receives a message, does something with it, and the world changes — or does not. Three things can happen when a method is called: it can return a value, it can change internal state, or it can send messages to other objects. Everything in unit testing flows from these three primitives.
This catalog names each pattern, explains the problem that arises without it, and shows you the solution in Flutter/Dart — production class first, test second. Like the Gang of Four, each entry stands alone. Read them in order or jump to what you need from the Catalog Index above.
Every chapter follows the same GoF-inspired structure. If you encounter a term you do not recognise, the Glossary defines every key word used in this catalog.
| Section | Purpose |
|---|---|
| Intent | One sentence: what this test pattern verifies |
| The Problem | The scenario that hurts without this pattern |
| Forces | The tensions you are navigating |
| Solution | The pattern in prose, followed by Dart code |
| Consequences | What you gain and what you give up |
| Implementation Notes | Dart/flutter_test-specific tips |
| Related Patterns | Other entries in this catalog |
Code for each chapter lives in its code/ subfolder — a production .dart file and a _test.dart file side by side.
This catalog is a Flutter package. The test files live alongside the production code inside each chapter's code/ folder rather than in a top-level test/ directory, so flutter test needs to be pointed at them explicitly.
To run every test in the catalog:
flutter pub get
flutter test $(find . -name "*_test.dart" -not -path "*/build/*")To run a single chapter's tests:
flutter test chapter01_receiving_responding/code/message_formatter_test.dartDependencies are declared in pubspec.yaml. Run flutter pub get once before running tests.
The patterns in this book are scoped to unit tests for plain Dart classes — the layer where business logic lives. Several adjacent categories of testing are intentionally outside that scope. They are not less important; they answer different questions and use different oracles. Naming them here is meant to set expectations, not to dismiss them.
-
Widget tests (
testWidgets) — Tests that mount a widget tree in a simulated rendering environment and verify layout, tap behavior, or accessibility. Everytest()call in this catalog operates on a plain class; nothing renders on screen. For the widget layer, see the Flutter widget testing documentation. -
Golden / snapshot tests — Pixel-level regression tests for rendered widgets. The oracle is an image comparison, not an
expect()matcher. These belong in the same suite as widget tests and require the same rendering pipeline. -
Integration tests (
integration_testpackage) — End-to-end tests that drive a real app on a device or emulator. They verify that the assembled application behaves correctly, not that any individual class fulfills its contract. The patterns in this catalog are about the second question; integration tests answer the first. -
Property-based testing (e.g.
glados) — Generative testing where the framework fabricates inputs to find counterexamples. Complementary rather than excluded: every pattern here could be reinforced with a property-based test. The catalog uses example-based assertions throughout because they are the form most teams write first and most reliably. -
Test-first vs test-last workflow — Whether tests are written before, after, or alongside production code is a workflow choice. The patterns work regardless. The Introduction discusses this briefly; this catalog takes no position.
-
State-management framework testing (bloc / riverpod / provider / GetX) — Testing notifiers, blocs, and providers requires conventions specific to each framework (e.g.
bloc_test'sblocTest()helper,ProviderContainerfor Riverpod). The general patterns here apply to the classes those frameworks wrap, but framework-specific helpers are not covered. -
Mocking libraries (
mocktail,mockito) — Every test in this catalog hand-writes its stubs and mocks to keep the seam visible. In a real codebase,mocktailremoves most of the boilerplate. The patterns translate directly; the syntax changes.
Chapter 5 is itself a mini-catalog. The nine entries in the Catalog Index cover every aspect of testing error handling:
| Entry | What It Tests | Read |
|---|---|---|
| E1 | That an exception is thrown at all | → |
| E2 | That the correct exception type is thrown | → |
| E3 | That the exception carries the correct message | → |
| E4 | That the calling code handles the exception correctly | → |
| E5 | That the object is in a consistent state after an exception | → |
| E6 | That a fallback path is taken when the primary path fails | → |
| E7 | That invalid inputs are rejected before reaching business logic | → |
| E8 | That the system behaves correctly at boundary values | → |
| E9 | That resources are released even when an exception occurs | → |
Unsure how E7 differs from E8? See validation vs boundaries.
- Catalog Index — All chapters and patterns
- Glossary — Definitions for every key term: stub, mock, fake, spy, SUT, observable behavior, and more.
- License — Free to use and share. If you publish a book or similar work derived from this catalog, credit the original (name + link).
- Social media assets — Share images for GitHub, Twitter/X, LinkedIn, and square posts
If your classes have hard-coded dependencies and you are not sure how to make them testable before applying the patterns, see Designing for Testability.