Expert · Responsive design and testing · topic 40 of 50 · 30 min
Unit tests
Explanation
Why unit tests?
A unit test runs a small piece of code (a function or a class) with known inputs and checks the output. Unit tests are fast (milliseconds), need no device, and catch logic bugs early: a wrong GST calculation, a validator that accepts a 5-digit PIN, a cart that miscounts after removing an item. Flutter projects include the flutter_test package for this.
// lib/cart.dart
class Cart {
final Map<String, int> _qty = {};
void add(String item) => _qty[item] = (_qty[item] ?? 0) + 1;
void remove(String item) {
final q = _qty[item];
if (q == null) return;
if (q <= 1) {
_qty.remove(item);
} else {
_qty[item] = q - 1;
}
}
int get count => _qty.values.fold(0, (a, b) => a + b);
}// test/cart_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:chai_shop/cart.dart';
void main() {
group('Cart', () {
late Cart cart;
setUp(() => cart = Cart()); // fresh cart before each test
test('starts empty', () {
expect(cart.count, 0);
});
test('adding the same item twice counts two', () {
cart.add('chai');
cart.add('chai');
expect(cart.count, equals(2));
});
test('removing an item that is not in the cart does nothing', () {
cart.remove('samosa');
expect(cart.count, 0);
});
});
}flutter test # all tests
flutter test test/cart_test.dart # one fileA good test name reads like a sentence describing behaviour: "adding the same item twice counts two". When it fails, the name alone tells you what broke. Each test follows the same shape: arrange (set up data), act (call the code), assert (check the result with expect).
What should you test first? The logic that would hurt most if it were wrong: money calculations, validators, discount rules, and state classes such as a ChangeNotifier, Notifier or Cubit. These are plain Dart classes, so they are easy to test without any widgets. Keeping logic out of widgets is what makes this possible.
Matchers
| Matcher | Checks |
|---|---|
| equals(x) (or just x) | Value equality |
| isTrue / isFalse / isNull / isNotNull | Booleans and nulls |
| contains(x) / hasLength(n) | Collections and strings |
| closeTo(value, delta) | Doubles with tolerance |
| throwsA(isA<ArgumentError>()) | That a function throws a certain error |
test('rejects a negative amount', () {
expect(() => Wallet().add(-50), throwsA(isA<ArgumentError>()));
});
test('async functions can be tested too', () async {
final price = await fetchPriceFake('chai');
expect(price, 15);
});Fakes and mocks
Code that talks to the network or a database should not do so in unit tests: slow, flaky and dependent on the internet. Depend on an abstract class (module 2), and in tests pass a fake implementation that returns fixed data. Libraries such as mocktail create mocks whose answers you configure per test and which can verify calls. Start with simple hand-written fakes.
Common mistakes
- Tests that depend on each other's data. Use setUp for a fresh state.
- Comparing doubles with equals: 0.1 + 0.2 is not exactly 0.3. Use closeTo.
- Calling real APIs in unit tests.
- Only testing the happy path; test edges like empty input, zero and maximum values.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Unit tests · 1/6
Unit tests check small pieces of Dart logic quickly, without a device.
Prefer a video by a teacher? Find free videos on “flutter unit testing test expect group setup mocktail” on YouTube ↗
In real life: Testing GST and validators
An invoicing app's GST calculator and form validators are pure functions, each covered by unit tests: 18% on Rs 1,000 is Rs 180, rounding to paise is correct, a PIN starting with 0 is rejected, and an empty GSTIN shows the right message. The whole suite runs in two seconds on every change, so a refactor that breaks tax maths is caught immediately.
int gstPaise(int amountPaise, int ratePercent) => (amountPaise * ratePercent / 100).round();
void main() {
test('18% GST on Rs 1000', () => expect(gstPaise(100000, 18), 18000));
test('rounds to the nearest paisa', () => expect(gstPaise(999, 18), 180));
}Practice (0/3 done)
How do you test that a function throws an ArgumentError?
Which matcher should compare 0.1 + 0.2 with 0.3?
Write unit tests for three things: the Cart class from this topic (add, remove, count, removing an absent item), a validateMobile function (empty, 9 digits, starting with 5, valid), and an EMI function with closeTo for a known value. Group them, use setUp, and run flutter test.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Widget testsResponsive design and testing · same module
- Integration tests and CIResponsive design and testing · same module
- Unit testing: checking one piece at a timeBackend Development · Testing and API documentation
- Test doubles: fakes, stubs and mocksC# and .NET · Testing, tooling and Unity
- Testing basics: unit, integration and end-to-endFrontend Web Development · Fast, accessible, findable and safe: quality for real sites
- Unit testing with JUnitJava Programming (Core to Spring Boot) · JDBC, Maven, Gradle and JUnit
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Where do unit test files go in a Flutter project?
Answer: test/
flutter test runs files in test/.
What does setUp do?
Answer: Runs before each test to prepare fresh data
Keeps tests independent.
Why avoid real network calls in unit tests?
Answer: They are slow and flaky and depend on the internet
Replace the API with a fake for fast, reliable tests.
Which package is commonly used for mocks in Dart tests?
Answer: mocktail
mocktail creates configurable mocks.
Your code sends real SMS messages. How should a unit test handle this?Related
Answer: Pass in a fake SMS sender that only records messages
A fake (a test double) stands in for the slow or risky part, and the test checks what the code asked it to do.
What is a risk of over-mocking?Related
Answer: Tests mirror the implementation and break on every harmless refactor
Test behaviour and outcomes, not every internal call.
Why mock fetch in a unit test?Related
Answer: So the test is fast, works offline and gets the same answer every time
Real network calls are slow and can change or fail, which makes tests flaky.
What makes a test "flaky"?Related
Answer: It sometimes fails without any code change
Usually time, randomness, network or shared state.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Responsive design and testing” before its exam: Widget tests, Integration tests and CI.
- Module 10 exam: Responsive design and testing · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Publishing to the stores and the web. 50 topics left in the course before the final exam.