Upgrade to Pro — share decks privately, control downloads, hide ads and more …

expect(isTestingInFlutterEasy, isTrue);

C887ad592770a197f114d0a1d3e3a5a7?s=47 Jorge Coca
October 09, 2020

expect(isTestingInFlutterEasy, isTrue);

Flutter is not the new kid on the block anymore; it has been around us for a quite a few years already, helping indie developers, startups and big corporations to bring value to their customers faster than ever, with beautiful experiences that are breathtaking... but how easy is to maintain that software? Is the testing framework as amazing as the UI framework?
Well, SPOILER ALERT: the answer is YES! I've had the pleasure to work on some of the biggest Flutter applications in the market, and testing has been always a key element that helped us to be successful.
In this talk, I'd like to share with you all the things you need to know to test your Flutter application efficiently and in record time, so you can focus on bringing a smile to your customer's face while ensuring that your project maintains an internal high level of quality.

C887ad592770a197f114d0a1d3e3a5a7?s=128

Jorge Coca

October 09, 2020
Tweet

Transcript

  1. None
  2. Our app...

  3. Types of tes*ng we can do on Flu4er

  4. Types of tes*ng we can do on Flu4er • Unit

    tests for pure Dart only classes
  5. Types of tes*ng we can do on Flu4er • Unit

    tests for pure Dart only classes • Widget tests for isolated widgets
  6. Types of tes*ng we can do on Flu4er • Unit

    tests for pure Dart only classes • Widget tests for isolated widgets • Integra.on tests for the applica6on as a whole
  7. None
  8. None
  9. None
  10. None
  11. There's no tests!!!

  12. Let's start wri+ng some unit tests...

  13. None
  14. None
  15. None
  16. None
  17. None
  18. Let's recap for a second

  19. Let's recap for a second • Tests start with main

  20. Let's recap for a second • Tests start with main

    • test keyword for each individual unit test
  21. Let's recap for a second • Tests start with main

    • test keyword for each individual unit test • flutter_test as part of the SDK
  22. Let's recap for a second • Tests start with main

    • test keyword for each individual unit test • flutter_test as part of the SDK • Use expect to run an asser;on
  23. Let's recap for a second • Tests start with main

    • test keyword for each individual unit test • flutter_test as part of the SDK • Use expect to run an asser;on • flutter test on CLI to run tests
  24. How do we test throwing excep1ons? !

  25. None
  26. Can we refactor our tests a li0le bit?

  27. Yes!

  28. None
  29. The test framework offers...

  30. The test framework offers... • setUpAll runs ONLY ONCE BEFORE

    any test is executed
  31. The test framework offers... • setUpAll runs ONLY ONCE BEFORE

    any test is executed • setUp runs ONCE BEFORE every test
  32. The test framework offers... • setUpAll runs ONLY ONCE BEFORE

    any test is executed • setUp runs ONCE BEFORE every test • tearDown runs ONCE AFTER every test
  33. The test framework offers... • setUpAll runs ONLY ONCE BEFORE

    any test is executed • setUp runs ONCE BEFORE every test • tearDown runs ONCE AFTER every test • tearDownAll runs ONLY ONCE AFTER all test have been executed
  34. None
  35. Let's con*nue tes*ng the public API of the GuessGame class

  36. None
  37. None
  38. Did you no)ce the amazing thing we just did?

  39. Let's look again!

  40. None
  41. We just tested asynchronous code!

  42. async and await are your new test best friends!

  43. None
  44. Can we refactor our tests a li0le bit?

  45. None
  46. We can organize our test file a bit be2er by

    using group()
  47. None
  48. We have completed the unit tests for the GuessGame class

  49. Let's recap...

  50. Let's recap... • Create a unit tests using the test

    method
  51. Let's recap... • Create a unit tests using the test

    method • Use expect to make asser3ons
  52. Let's recap... • Create a unit tests using the test

    method • Use expect to make asser3ons • Test lifecyle: setUpAll, setUp, tearDown and tearDownAl
  53. Let's recap... • Create a unit tests using the test

    method • Use expect to make asser3ons • Test lifecyle: setUpAll, setUp, tearDown and tearDownAl • Async tests just by using async and await
  54. Let's recap... • Create a unit tests using the test

    method • Use expect to make asser3ons • Test lifecyle: setUpAll, setUp, tearDown and tearDownAl • Async tests just by using async and await • Organize your test file using group
  55. Let's add widget tests for our Flu2er applica5on

  56. What are our widgets? What's their mission?

  57. AndroidSummitApp2020App

  58. AndroidSummitApp2020App • Takes a GuessGame as a dependency in the

    constructor, but it does nothing with it
  59. AndroidSummitApp2020App • Takes a GuessGame as a dependency in the

    constructor, but it does nothing with it • No need to mock it
  60. AndroidSummitApp2020App • Takes a GuessGame as a dependency in the

    constructor, but it does nothing with it • No need to mock it • Creates a MaterialApp
  61. AndroidSummitApp2020App • Takes a GuessGame as a dependency in the

    constructor, but it does nothing with it • No need to mock it • Creates a MaterialApp • We can find this widget
  62. AndroidSummitApp2020App • Takes a GuessGame as a dependency in the

    constructor, but it does nothing with it • No need to mock it • Creates a MaterialApp • We can find this widget • Renders the Questionnaire widget immediately
  63. AndroidSummitApp2020App • Takes a GuessGame as a dependency in the

    constructor, but it does nothing with it • No need to mock it • Creates a MaterialApp • We can find this widget • Renders the Questionnaire widget immediately • We can find this widget
  64. None
  65. Ques%onnaire

  66. Ques%onnaire • Takes a GuessGame as a dependency in the

    constructor that controls the state
  67. Ques%onnaire • Takes a GuessGame as a dependency in the

    constructor that controls the state • We can mock it to control behavior
  68. None
  69. Where do we start?

  70. None
  71. None
  72. In widget tests...

  73. In widget tests... • We use testWidgets instead of test

  74. In widget tests... • We use testWidgets instead of test

    • It provides a WidgetTester to interact with the user interface (UI)
  75. In widget tests... • We use testWidgets instead of test

    • It provides a WidgetTester to interact with the user interface (UI) • We use Finders to locate widgets on the test UI
  76. In widget tests... • We use testWidgets instead of test

    • It provides a WidgetTester to interact with the user interface (UI) • We use Finders to locate widgets on the test UI • There are special matchers: findsOneWidget, findsNWidgets, findsNothing...
  77. None
  78. That's pre+y much all the tests we can write for

    AndroidSummit2020App
  79. Let's test the Questionnaire

  80. None
  81. Mockito

  82. Mockito • Since GuessGame is a dependency, we are going

    to mock its behavior using mockito
  83. Mockito • Since GuessGame is a dependency, we are going

    to mock its behavior using mockito • The syntax is as follows:
  84. Mockito • Since GuessGame is a dependency, we are going

    to mock its behavior using mockito • The syntax is as follows: • We use when(...).thenReturn(...) to control synchronous behavior
  85. Mockito • Since GuessGame is a dependency, we are going

    to mock its behavior using mockito • The syntax is as follows: • We use when(...).thenReturn(...) to control synchronous behavior • We use when(...).thenAnswer(...) to control asynchronous behavior
  86. None
  87. Why do we mock?

  88. Why do we mock? • We care about the public

    API contract, not about the implementa6on details
  89. Why do we mock? • We care about the public

    API contract, not about the implementa6on details • It'd be tedious if GuessGame had 100 ques6ons
  90. Why do we mock? • We care about the public

    API contract, not about the implementa6on details • It'd be tedious if GuessGame had 100 ques6ons • We can simulate successes and errors without knowing the internals of our dependencies
  91. Let's finish the test!

  92. Let's remember our UI

  93. None
  94. Our tests failed

  95. Questionnaire renders the question and the final score when the

    game is completed: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ The following assertion was thrown building Text("Test question 1", debugLabel: (englishLike display1 2014).merge(blackMountainView headline4), inherit: false, color: Color(0x8a000000), family: Roboto, size: 34.0, weight: 400, baseline: alphabetic, decoration: TextDecoration.none): No Directionality widget found. RichText widgets require a Directionality widget ancestor. The specific widget that could not find a Directionality ancestor was: RichText The ownership chain for the affected widget is: "RichText ← Text ← Column ← _Question ← Questionnaire ← [root]" Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the top of your application widget tree. It determines the ambient reading direction and is used, for example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.
  96. What are we missing?

  97. An App widget that serves as Scaffold

  98. Let's fix it!

  99. None
  100. What the *&%$! It cannot find the "Test question 2"

    text?
  101. What is going on?

  102. What is going on? • Between ques+on and ques+on there's

    a state change
  103. What is going on? • Between ques+on and ques+on there's

    a state change • We need to ask the test system to paint a new frame a;er the state change
  104. What is going on? • Between ques+on and ques+on there's

    a state change • We need to ask the test system to paint a new frame a;er the state change • We use pump and pumpAndSettle for these situa+ons
  105. Let's fix it!

  106. None
  107. Wohoooo!

  108. All unit and widget tests are passing!

  109. ...but

  110. How do we know if we are missing something?

  111. flutter test --coverage

  112. None
  113. What is that?

  114. None
  115. None
  116. Not bad...

  117. So far we have...

  118. So far we have... • ✅ Unit tests

  119. So far we have... • ✅ Unit tests • ✅

    Widget tests
  120. So far we have... • ✅ Unit tests • ✅

    Widget tests • ❌ Integra/on tests
  121. What do we need to run integra/on tests?

  122. What do we need to run integra/on tests? • A

    new folder for integra0on test: test_driver
  123. What do we need to run integra/on tests? • A

    new folder for integra0on test: test_driver • A new dart file called app.dart that will contain an "instrumented" version of our app
  124. What do we need to run integra/on tests? • A

    new folder for integra0on test: test_driver • A new dart file called app.dart that will contain an "instrumented" version of our app • A new dart file called app_test.dart that will contain the scripted tests that will exercise our applica0on using FlutterDriver
  125. What do we need to run integra/on tests? • A

    new folder for integra0on test: test_driver • A new dart file called app.dart that will contain an "instrumented" version of our app • A new dart file called app_test.dart that will contain the scripted tests that will exercise our applica0on using FlutterDriver • We cannot use any internal details of our applica0on (blind tests)
  126. None
  127. None
  128. None
  129. None
  130. To run integra,on tests... flutter driver --target=test_driver/app.dart --driver=test_driver/app_test.dart

  131. Demo &me!

  132. That was fast!

  133. So far we have...

  134. So far we have... • ✅ Unit tests

  135. So far we have... • ✅ Unit tests • ✅

    Widget tests
  136. So far we have... • ✅ Unit tests • ✅

    Widget tests • ✅ Integra/on tests
  137. Where to go from here!?

  138. None
  139. None
  140. Automa'on for the win!

  141. Here's a surprise for you!

  142. It is &me to say "see you later!"

  143. It is &me to say "see you later!" • !

    Very Good Ventures
  144. It is &me to say "see you later!" • !

    Very Good Ventures • " GDE for Flu3er
  145. It is &me to say "see you later!" • !

    Very Good Ventures • " GDE for Flu3er • # @jcocaramos
  146. It is &me to say "see you later!" • !

    Very Good Ventures • " GDE for Flu3er • # @jcocaramos • $ h3ps:/ /github.com/jorgecoca/androidsummit2020
  147. Thank you!