1 of 52

Flutter Tests

Perché sono importanti e come si creano

Carlo Lucera

@hat_droid

Matteo Maria Terzuolo

@SaltySpag

2 of 52

3 of 52

Test?

4 of 52

Why testing (Pub edition)

QA Engineer

Real customer

  • Ask if he can use the bathroom
  • Orders a Beer
  • Orders 2 Beers
  • Orders 99999 Beers
  • Orders a BEE
  • Orders a Bear
  • Orders a 329wevii94-
  • Orders -1 beers

Tests passed!

5 of 52

6 of 52

How many types?

7 of 52

Test types

  • Manual Testing (QA Engineer)
  • Unit test
  • Widget test
  • Integration test
  • Testing in production

NO

8 of 52

Unit Tests

9 of 52

1+1 → ???

Unit tests

Simple testing, used for single methods or class.

10 of 52

Widget Tests

11 of 52

Widget tests

Test for a single Widget.

CardWidget("Test"),

12 of 52

Integration Tests

13 of 52

Splash → Login? → Home

Integration tests

Testing a complete flow or large part of an application

14 of 52

Test anatomy in Flutter

  • my_app/
    • lib/
      • Counter.dart
    • test/
      • counter_test.dart

15 of 52

Unit Tests Implementation

16 of 52

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)

17 of 52

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

18 of 52

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

19 of 52

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

20 of 52

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

21 of 52

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

22 of 52

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

23 of 52

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

24 of 52

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)

25 of 52

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)

26 of 52

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

27 of 52

Widgets Tests Implementation

28 of 52

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

29 of 52

  • Is provided text shown?
  • It is wrapped with a card?
  • There is a Padding around text?
  • Is the elevation applied?

Widgets Tests

30 of 52

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);

},

);

  • Is provided text shown?

31 of 52

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);

},

);

  • Is provided text shown?

32 of 52

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);

},

);

  • Is provided text shown?

33 of 52

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);

},

);

  • Is provided text shown?

34 of 52

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);

},

);

  • Is provided text shown?

35 of 52

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);

},

);

  • Is provided text shown?

36 of 52

Matchers

37 of 52

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>

38 of 52

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);

39 of 52

Support for specifying test expectations.

  • findsOneWidget
  • findsNothing
  • findsNWidgets
  • findsAtLeastNWidgets

Matchers

40 of 52

  • Is Card wrapping text?

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);

},

);

41 of 52

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);

},

);

  • Is Card wrapping text?

42 of 52

Widgets Tests

expect(find.byType(Card), findsOneWidget);

expect(

find.ancestor(

of: find.text(testText),

matching: find.byType(Card),

),

findsOneWidget,

);

  • Is Card wrapping text?

43 of 52

Finders

44 of 52

Provides lightweight syntax for getting frequently used widget

  • find.text(...)
  • find.byType(...)
  • find.byKey(...)
  • find.ancestor(of: …, matching: …)
  • find.descendant(of: …, matching: …)
  • find.byIcon(...)

Finders

45 of 52

Integration Tests Implementation

46 of 52

47 of 52

48 of 52

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

49 of 52

50 of 52

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

51 of 52

Grazie per l’attenzione

Domande?

52 of 52

Contatti

Matteo Maria Terzuolo

Twitter: @SaltySpag

Carlo Lucera

Twitter: @hat_droid

Flutter Italia Developers

Telegram: t.me/flutteritdevs

Link slide