Tests and lints are what keep a Dart codebase or Flutter app maintainable as it grows.
package:test is the standard test framework (Flutter's flutter_test builds on it). Tests live in test/ in files ending _test.dart, and run with dart test.
test('adds', () => expect(add(1, 2), 3));Structure with group('name', () { ... }), and share fixtures with setUp / tearDown (per test) and setUpAll / tearDownAll.
expect(actual, matcher) accepts values or matchers: equals, isTrue, isNull, contains, hasLength, closeTo(3.14, 0.01), isA<FormatException>(), throwsA(...), throwsArgumentError, completion(...) for futures and emitsInOrder([...]) for streams.
Async tests just return a future: mark the body async and await. For code that throws, wrap the call: expect(() => parse('x'), throwsFormatException).
Run a subset with dart test --name 'parser' or a single file. Use package:mocktail or mockito for fakes, though a hand-written class that implements the dependency is often simpler.
Static analysis catches problems without running code: dart analyze reports errors, warnings and lints configured in analysis_options.yaml. Include package:lints/recommended.yaml (or flutter_lints) and add rules like unawaited_futures and prefer_final_locals. dart format gives one canonical style, and dart fix --apply auto-applies many suggestions. Run all three in CI.
Example
// test/cart_test.dart
import 'package:test/test.dart';
class Cart {
final _items = <String, double>{};
void add(String name, double price) {
if (price < 0) throw ArgumentError.value(price, 'price');
_items[name] = price;
}
double get total => _items.values.fold(0, (a, b) => a + b);
Future<String> checkout() async => 'paid ${total.toStringAsFixed(2)}';
}
void main() {
group('Cart', () {
late Cart cart;
setUp(() => cart = Cart()..add('tea', 2.5));
test('sums prices', () {
cart.add('cake', 0.1);
expect(cart.total, closeTo(2.6, 1e-9));
});
test('rejects negative prices', () {
expect(() => cart.add('x', -1), throwsArgumentError);
});
test('checks out', () async => expect(await cart.checkout(), 'paid 2.50'));
});
}Remember
test,group,setUpandexpectwith matchers- Async tests are
asyncfunctions; wrap throwing calls in a closure - Run
dart analyze,dart formatand tests in CI
Watch out for
- Writing
expect(parse('x'), throwsA(...))without the() =>closure, so the exception is thrown before expect sees it.