This episode covers Dart testing: writing unit tests with package:test, grouping and setup-teardown, using matchers, mocking dependencies, integration tests, and running all tests with dart test.

Code without tests is a time bomb. Episode 9 brings you into testing and quality practice in Dart: writing unit tests with package:test, grouping tests, leveraging setup and teardown, using matchers for expressive assertions, and testing code that depends on external services through mocking.
Testing isn't just an extra task — it's the fastest feedback that lets you change code with confidence. The faster tests run, the more often you'll run them.
By the end of this episode, your project will have a test foundation that can grow alongside your code.
Make sure test is in dev_dependencies, then write test files in the test/ directory:
import 'package:test/test.dart';
int tambah(int a, int b) => a + b;
void main() {
test('tambah dua angka', () {
expect(tambah(2, 3), 5);
});
}The test('description', () {...}) function defines a single test case, and expect(value, matcher) verifies the result. Run it with:
dart testdart test finds and runs all files in the test/ directory, then shows a summary of passes and failures.
Matchers make assertions more expressive than merely comparing values:
import 'package:test/test.dart';
void main() {
test('beragam asersi', () {
expect([1, 2, 3], contains(2));
expect('halo', startsWith('ha'));
expect(() => throw Exception('x'), throwsException);
});
}contains(2) checks membership in a collection, startsWith('ha') checks a string prefix, and throwsException ensures a function throws an error. Matchers are composable, so complex assertions stay readable.
Group related tests with group so the results report is more structured:
import 'package:test/test.dart';
void main() {
group('Kalkulator', () {
test('tambah', () {
expect(1 + 1, 2);
});
test('kurang', () {
expect(5 - 2, 3);
});
});
}group('Kalkulator', ...) wraps tests with the same theme. When there's a failure, the report shows the specific group and test name.
Use setUp to prepare shared state and tearDown to clean it up:
import 'package:test/test.dart';
late List<String> penyimpanan;
void main() {
setUp(() {
penyimpanan = [];
});
test('menyimpan data', () {
penyimpanan.add('item');
expect(penyimpanan.length, 1);
});
}setUp(() {...}) runs before every test, so each test starts with clean state. tearDown is useful for closing connections or deleting temporary files.
When a unit must not touch the network or database, mock its dependencies. Use package:mockito combined with build_runner:
import 'package:mockito/mockito.dart';
class KlienApi {
Future<String> ambilData() async => 'nyata';
}
class MockKlienApi extends Mock implements KlienApi {}
void main() {
test('mock mengembalikan data palsu', () async {
var klien = MockKlienApi();
when(klien.ambilData()).thenAnswer((_) async => 'palsu');
expect(await klien.ambilData(), 'palsu');
});
}when(klien.ambilData()).thenAnswer(...) programs the mock's behavior without running the real implementation. This makes tests fast and deterministic.
Integration tests verify cooperation between real components — for example, a local HTTP server that actually runs:
dart test test/integration/dart test test/integration/ runs only the tests in the integration subdirectory. Keep fast unit tests separate from slow integration tests so the suite stays lightweight.
Coverage gives you a picture of how much code is executed by tests:
dart test --coverage=coverage
dart pub global activate coverage
format_coverage --lcov --in=coverage --out=coverage/lcov.infoAfter dart test --coverage=coverage, the format_coverage tool converts the results into lcov format, which tools like coveralls can read. Aim for high coverage in business logic, not just chasing a global percentage.
Key takeaways:
test() and expect() are the foundation of unit tests with package:test.contains, startsWith, and throwsException make assertions expressive.group organizes tests; setUp and tearDown keep state clean.dart test --coverage=coverage measures how much code is covered.In the next episode 10, we'll cover server-side Dart and backend — building an HTTP server with Shelf, routing and middleware, JSON serialization and persistence, and deploying a Dart server application to production.