Flutter Tests
Perché sono importanti e come si creano
Carlo Lucera
@hat_droid
Matteo Maria Terzuolo
@SaltySpag
Test?
Why testing (Pub edition)
QA Engineer
Real customer
Tests passed!
How many types?
Test types
NO
Unit Tests
1+1 → ???
Unit tests
Simple testing, used for single methods or class.
Widget Tests
Widget tests
Test for a single Widget.
CardWidget("Test"),
Integration Tests
Splash → Login? → Home
Integration tests
Testing a complete flow or large part of an application
Test anatomy in Flutter
Unit Tests Implementation
class Point {
int x;
int y;
Point(this.x, this.y);
void translateX(int amount) {
x = x + amount;
}
void translateY(int amount) {
x = x + amount;
}
void translate(int xAmount, int yAmount) {
translateX(xAmount);
translateY(yAmount);
}
}
Unit Tests
(0,0)
test(
"Test translate X",
() {
const initialX = 0;
const translation = 2;
final point = Point(initialX, 0);
point.translateX(translation);
expect(point.x, equals(initialX + translation));
},
);
Simple unit test
test(
"Test translate X",
() {
const initialX = 0;
const translation = 2;
final point = Point(initialX, 0);
point.translateX(translation);
expect(point.x, equals(initialX + translation));
},
);
Simple unit test
test(
"Test translate X",
() {
const initialX = 0;
const translation = 2;
final point = Point(initialX, 0);
point.translateX(translation);
expect(point.x, equals(initialX + translation));
},
);
Simple unit test
test(
"Test translate X",
() {
const initialX = 0;
const translation = 2;
final point = Point(initialX, 0);
point.translateX(translation);
expect(point.x, equals(initialX + translation));
},
);
Simple unit test
test(
"Test translate X",
() {
const initialX = 0;
const translation = 2;
final point = Point(initialX, 0);
point.translateX(translation);
expect(point.x, equals(initialX + translation));
},
);
Simple unit test
test(
"Test translate X",
() {
const initialX = 0;
const translation = 2;
final point = Point(initialX, 0);
point.translateX(translation);
expect(point.x, equals(initialX + translation));
},
);
Simple unit test
Test passed: 1 of 1 test - 38 ms
test(
"Test translate Y",
() {
const initialY = 0;
const translation = 2;
final point = Point(0, initialY);
point.translateY(translation);
expect(point.y, equals(initialY + translation));
},
);
Simple unit test
Test failed: 1 of 2 test - 38 ms
class Point {
int x;
int y;
Point(this.x, this.y);
void translateX(int amount) {
x = x + amount;
}
void translate(int xAmount, int yAmount) {
x + xAmount;
y + yAmount;
}
}
void translateY(int amount) {
x = x + amount;
}
class Point {
int x;
int y;
Point(this.x, this.y);
void translateX(int amount) {
x = x + amount;
}
void translateY(int amount) {
x = x + amount;
}
void translate(int xAmount, int yAmount) {
x + xAmount;
y + yAmount;
}
}
Unit Tests
(0,0)
class Point {
int x;
int y;
Point(this.x, this.y);
void translateX(int amount) {
x = x + amount;
}
void translateY(int amount) {
y = y + amount;
}
void translate(int xAmount, int yAmount) {
x + xAmount;
y + yAmount;
}
}
Unit Tests
(0,0)
test(
"Test translate Y",
() {
const initialY = 0;
const translation = 2;
final point = Point(0, initialY);
point.translateY(translation);
expect(point.y, equals(initialY + translation));
},
);
Simple unit test
Test passed: 2 of 2 test - 38 ms
Widgets Tests Implementation
class CardWidget extends StatelessWidget {
const CardWidget(this.text, {super.key});
final String text;
@override
Widget build(BuildContext context) {
return Card(
elevation: 5,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(text),
),
);
}
}
Widgets Tests
Widgets Tests
Widgets Tests
testWidgets(
'Card show provided text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.text(testText), findsOneWidget);
},
);
Widgets Tests
testWidgets(
'Card show provided text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.text(testText), findsOneWidget);
},
);
Widgets Tests
testWidgets(
'Card show provided text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.text(testText), findsOneWidget);
},
);
Widgets Tests
testWidgets(
'Card show provided text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.text(testText), findsOneWidget);
},
);
Widgets Tests
testWidgets(
'Card show provided text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.text(testText), findsOneWidget);
},
);
Widgets Tests
testWidgets(
'Card show provided text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.text(testText), findsOneWidget);
},
);
Matchers
expect(find.text(testText).evaluate().length, 1);
Matchers
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞═══════════
The following TestFailure was thrown running a test:
Expected: <1>
Actual: <0>
Matchers
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞═══════════
The following TestFailure was thrown running a test:
Expected: exactly one matching node in the widget tree
Actual: _TextFinder:<zero widgets with text "Test" (ignoring offstage widgets)>
Which: means none were found but one was expected
expect(find.text(testText), findsOneWidget);
Support for specifying test expectations.
Matchers
Widgets Tests
testWidgets(
'Card is used above text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.byType(Card), findsOneWidget);
},
);
Widgets Tests
testWidgets(
'Card is used above text',
(WidgetTester tester) async {
const String testText = "Test";
await tester.pumpWidget(
const MaterialApp(
home: CardWidget(testText),
),
);
expect(find.byType(Card), findsOneWidget);
},
);
Widgets Tests
expect(find.byType(Card), findsOneWidget);
expect(
find.ancestor(
of: find.text(testText),
matching: find.byType(Card),
),
findsOneWidget,
);
Finders
Provides lightweight syntax for getting frequently used widget
Finders
Integration Tests Implementation
await tester.pumpWidget(const MyApp());
final tilesFinder = find.byType(TripStepTile);
expect(tilesFinder, findsAtLeastNWidgets(1));
await tester.tap(find.byType(Checkbox).first);
await tester.pumpAndSettle();
final progressFinder = find.byType(LinearProgressIndicator);
final progressIndicator = tester.widget<LinearProgressIndicator>(progressFinder);
final tilesAmount = tester.elementList(tilesFinder).length;
expect(progressIndicator.value, equals(1 / tilesAmount));
Integration Tests
const taskTitle = "Task name";
await tester.pumpWidget(const MyApp());
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
final titleInputFinder = find.widgetWithText(TextField, "Title");
await tester.enterText(titleInputFinder, taskTitle);
await tester.tap(find.text("Save"));
await tester.pumpAndSettle();
final taskFinder = find.widgetWithText(TripStepTile, taskTitle);
expect(taskFinder, findsOneWidget);
Integration Tests
Grazie per l’attenzione
Domande?
Contatti
Matteo Maria Terzuolo
Twitter: @SaltySpag
Carlo Lucera
Twitter: @hat_droid
Flutter Italia Developers
Telegram: t.me/flutteritdevs
Link slide