├── .gitignore ├── LICENSE ├── README.md └── recipes ├── flutter_driver ├── how_do_i_find_something │ ├── .gitignore │ ├── README.md │ ├── lib │ │ └── main.dart │ ├── pubspec.yaml │ └── test_driver │ │ ├── example.dart │ │ └── example_test.dart ├── how_do_i_pop_dialog │ ├── .gitignore │ ├── README.md │ ├── lib │ │ └── main.dart │ ├── pubspec.yaml │ └── test_driver │ │ ├── example.dart │ │ └── example_test.dart ├── how_do_i_run_a_flutter_driver_test │ ├── .gitignore │ ├── README.md │ ├── lib │ │ └── main.dart │ ├── pubspec.yaml │ └── test_driver │ │ ├── example.dart │ │ └── example_test.dart ├── how_do_i_run_a_script │ ├── .gitignore │ ├── README.md │ ├── echo.sh │ ├── lib │ │ └── main.dart │ ├── pubspec.yaml │ └── test_driver │ │ ├── example.dart │ │ └── example_test.dart ├── how_do_i_run_multiple_test_files_without_restarting_app │ ├── .gitignore │ ├── README.md │ ├── lib │ │ └── main.dart │ ├── pubspec.yaml │ ├── run_all_tests.sh │ └── test_driver │ │ ├── start_app.dart │ │ ├── suite1.dart │ │ ├── suite1_test.dart │ │ ├── suite2.dart │ │ └── suite2_test.dart └── how_do_i_take_a_screenshot │ ├── .gitignore │ ├── README.md │ ├── lib │ └── main.dart │ ├── my_screenshot.png │ ├── pubspec.yaml │ └── test_driver │ ├── example.dart │ └── example_test.dart └── flutter_test ├── how_do_i_drag_something ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_find_something ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_mock_async_http_request ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_mock_shared_preferences ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_open_a_drawer ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_run_a_flutter_test ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_run_a_script ├── .gitignore ├── README.md ├── echo.sh ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_send_a_keyboard_action ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_test_an_animation ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_test_an_exception ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── how_do_i_test_routes ├── .gitignore ├── README.md ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart └── what_if_i_need_build_context ├── .gitignore ├── README.md ├── lib └── main.dart ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.*~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Matthew Jaoudi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_test_cookbook 2 | 3 | A community driven cookbook with _recipes_ (i.e., examples) on how to test your flutter application. 4 | 5 | ## Recipes 6 | 7 | There are [three pillars](https://flutter.dev/docs/cookbook/testing) of flutter tests: 8 | 1) unit 9 | 2) widget 10 | 3) integration 11 | 12 | This cookbook is mostly concerned with 2 and 3. 13 | 14 | Currently, [flutter_test](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) is used for widget tests, and [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) is used for integration tests. However, it is possible that `flutter_driver` could eventually be deprecated, and `flutter_test` could be used for both widget and integration tests. 15 | 16 | We're moving away from flutter_driver in favour of extending flutter_test to work on devices. 17 | - Hixie 18 | https://github.com/flutter/flutter/issues/7474#issuecomment-558882182 19 | 20 | Because of this, the recipes will be split out into two directories: `flutter_test` and `flutter_driver` 21 | 22 | ### flutter_test recipes 23 | 24 | - [How do I run a flutter test?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_run_a_flutter_test) 25 | - [How do I find something?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_find_something) 26 | - [How do I test routes?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_test_routes) 27 | - [How do I drag something?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_drag_something) 28 | - [How do I open a Drawer?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_open_a_drawer) 29 | - [How do I send a keyboard action like done or next?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_send_a_keyboard_action) 30 | - [How do I test an Exception?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_test_an_exception) 31 | - [What if I need a BuildContext?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/what_if_i_need_build_context) 32 | - [How do I run a script inside a test?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_run_a_script) 33 | - [How do I mock an async http request?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_mock_async_http_request) 34 | - [How do I mock shared_preferences?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_mock_shared_preferences) 35 | - [How do I test an animation?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_test/how_do_i_test_an_animation) 36 | 37 | ### flutter_driver recipes 38 | 39 | - [How do I run a flutter driver test?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_driver/how_do_i_run_a_flutter_driver_test) 40 | - [How do I find something?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_driver/how_do_i_find_something) 41 | - [How do I take a screenshot](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_driver/how_do_i_take_a_screenshot) 42 | - [How do I dismiss (i.e. pop) a dialog?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_driver/how_do_i_pop_dialog) 43 | - [How do I run a script inside of a test?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_driver/how_do_i_run_a_script) 44 | - [How I run multiple test files without restarting app?](https://github.com/gadfly361/flutter_test_cookbook/blob/master/recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app) 45 | 46 | ## External Resources 47 | 48 | This section is a list of external resources that may be useful when exploring how to test your flutter application. 49 | 50 | *Written* 51 | - [Flutter UI Testing (with codemagic)](https://blog.codemagic.io/flutter-ui-testing/) 52 | - [Mock dependencies using Mockito](https://flutter.dev/docs/cookbook/testing/unit/mocking) 53 | - [Flutter: Golden tests - compare Widgets with Snapshots](https://medium.com/flutter-community/flutter-golden-tests-compare-widgets-with-snapshots-27f83f266cea) 54 | - [Testing gestures using Flutter driver](https://medium.com/flutter-community/testing-gestures-using-flutter-driver-b37981c24366) 55 | - [60 Days of Flutter: Day 4-5: Widget Testing with Flutter](https://medium.com/@adityadroid/60-days-of-flutter-day-4-5-widget-testing-with-flutter-a30236dd04fc) 56 | - [Blazingly Fast Flutter Driver Tests](https://medium.com/flutter-community/blazingly-fast-flutter-driver-tests-5e375c833aa) 57 | - [Developing and testing accessible apps in Flutter](https://medium.com/flutter-community/developing-and-testing-accessible-app-in-flutter-1dc1d33c7eea) 58 | 59 | 60 | *Videos* 61 | - [Flutter: Deep Dive with Widget Tests and Mockito](https://www.youtube.com/watch?v=75i5VmTI6A0) 62 | - [Bloc Test Tutorial - Easier Way to Test Blocs in Dart & Flutter](https://resocoder.com/2019/11/29/bloc-test-tutorial-easier-way-to-test-blocs-in-dart-flutter/) 63 | 64 | 65 | ## Want to contribute? 66 | 67 | First of all, thank you. Contributions are encouraged! Please follow the guidelines below. 68 | 69 | ### Want to suggest a recipe? 70 | 71 | If you'd like to suggest a recipe, please open an issue and add `[recipe-suggestion]` to the beginning of the title. 72 | 73 | ### Want to add your own recipe? 74 | 75 | If you'd like to add a recipe, please add `[new-recipe]` to the beginning of your PR's title. 76 | 77 | Each recipe should: 78 | 79 | - be focused on a specific topic 80 | - be concise 81 | - have runnable tests 82 | - only check in 'meaningful' files 83 | 84 | Note: if you want to get an early sense of whether or not your recipe will be accepted, please open a `[recipe-suggestion]` issue first. 85 | 86 | ### Want to fix a typo? 87 | 88 | If you'd like to fix a typo, please add `[fix-typo]` to the beginning of your PR's title. 89 | 90 | ### Want to correct an error in an existing recipe? 91 | 92 | If you'd like to correct an error in an existing recipe, please add `[fix-recipe]` to the beginning of your PR's title. 93 | 94 | ### Want to add a link to an external resource? 95 | 96 | If you'd like to add a link to an external resource, please add `[add-resource]` to the beginning of your PR's title. 97 | 98 | ## LICENSE 99 | 100 | Copyright © 2020 Matthew Jaoudi 101 | 102 | Distributed under the The MIT License (MIT). 103 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_find_something/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (Driver) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test/ -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_find_something/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I find something ... whether it be a widget, some text, etc? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Add [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) and [test](https://pub.dev/packages/test) to your `pubspec.yaml` file. 15 | 16 | ```yaml 17 | dev_dependencies: 18 | flutter_driver: 19 | sdk: flutter 20 | test: any 21 | ``` 22 | 23 | Then get the dependencies by running: 24 | 25 | ```sh 26 | flutter packages get 27 | ``` 28 | 29 | ## 3) Replace the `lib/main.dart` file 30 | 31 | Replace the `lib/main.dart` file with the following: 32 | 33 | ```dart 34 | import 'package:flutter/material.dart'; 35 | 36 | void main() => runApp(MyApp()); 37 | 38 | class MyApp extends StatelessWidget { 39 | @override 40 | Widget build(BuildContext context) { 41 | return MaterialApp( 42 | home: Scaffold( 43 | body: Column( 44 | children: [ 45 | RaisedButton( 46 | onPressed: () => print("rasied button was clicked"), 47 | child: Text("we will find this by searching for a type"), 48 | ), 49 | Text( 50 | "we will find this by searching for text", 51 | ), 52 | Text( 53 | "will will find this by searching for a key", 54 | key: Key("mykey"), 55 | ), 56 | ], 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | ``` 63 | 64 | ## 4) Create a `test_driver` directory. 65 | 66 | ```sh 67 | mkdir test_driver 68 | cd test_driver 69 | ``` 70 | 71 | ## 5) Add an `example.dart` file. 72 | 73 | Create a `test_driver/example.dart` file, and add the following to it: 74 | 75 | ```dart 76 | import 'package:flutter_driver/driver_extension.dart'; 77 | import 'package:ftc/main.dart' as app; 78 | 79 | void main() { 80 | // Add the following to leverage flutter_driver 81 | enableFlutterDriverExtension(); 82 | 83 | app.main(); 84 | } 85 | ``` 86 | 87 | ## 6) Add an `example_test.dart` file. 88 | 89 | Create a `test_driver/example_test.dart` file, and add the following to it: 90 | 91 | ```dart 92 | import 'package:flutter_driver/flutter_driver.dart'; 93 | import 'package:test/test.dart'; 94 | 95 | void main() { 96 | FlutterDriver driver; 97 | 98 | setUpAll(() async { 99 | driver = await FlutterDriver.connect(); 100 | }); 101 | 102 | tearDownAll(() async { 103 | if (driver != null) { 104 | await driver.close(); 105 | } 106 | }); 107 | 108 | test("let's find a widget by its type", () async { 109 | // There is a RaisedButton in the main.dart file 110 | SerializableFinder raisedButtonFinder = find.byType("RaisedButton"); 111 | 112 | await driver.waitFor(raisedButtonFinder); 113 | await driver.tap(raisedButtonFinder); 114 | }); 115 | 116 | test("let's find a specific string of text", () async { 117 | // This is the exact string of text found in a Text widget in the main.dart file 118 | SerializableFinder textFinder = 119 | find.text("we will find this by searching for text"); 120 | 121 | await driver.waitFor(textFinder); 122 | }); 123 | 124 | test("let's find a widget by its key", () async { 125 | // There is a widget with its key defined as `Key("mykey")` in the main.dart file 126 | SerializableFinder keyFinder = find.byValueKey("mykey"); 127 | 128 | await driver.waitFor(keyFinder); 129 | }); 130 | } 131 | ``` 132 | 133 | ## 7) Run the flutter driver tests 134 | 135 | ```sh 136 | flutter driver -t test_driver/example.dart 137 | ``` 138 | 139 | # Run tests from example code in cookbook itself 140 | 141 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 142 | 143 | ```sh 144 | flutter create . 145 | flutter packages get 146 | flutter driver -t test_driver/example.dart 147 | ``` 148 | 149 | # Outputs when recipe was made / updated 150 | 151 | ## Output of running flutter driver tests 152 | 153 | ```sh 154 | $ flutter driver -t test_driver/example.dart 155 | Using device iPhone 8. 156 | Starting application: test_driver/example.dart 157 | Running Xcode build... 158 | ├─Assembling Flutter resources... 9.5s 159 | └─Compiling, linking and signing... 4.4s 160 | Xcode build done. 15.4s 161 | flutter: Observatory listening on http://127.0.0.1:62625/uVmgCkrhJno=/ 162 | 00:00 +0: (setUpAll) 163 | 164 | [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:62625/uVmgCkrhJno=/ 165 | [trace] FlutterDriver: Isolate found with number: 2547406183923599 166 | [trace] FlutterDriver: Isolate is paused at start. 167 | [trace] FlutterDriver: Attempting to resume isolate 168 | [trace] FlutterDriver: Waiting for service extension 169 | [info ] FlutterDriver: Connected to Flutter application. 170 | 00:00 +0: let's find a widget by its type 171 | 172 | flutter: rasied button was clicked 173 | 00:00 +1: let's find a specific string of text 174 | 175 | 00:00 +2: let's find a widget by its key 176 | 177 | 00:00 +3: (tearDownAll) 178 | 179 | 00:00 +3: All tests passed! 180 | 181 | Stopping application instance. 182 | ``` 183 | 184 | ## Output of running `flutter doctor -v` 185 | 186 | ```sh 187 | $ flutter doctor -v 188 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 189 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 190 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 191 | • Engine revision 2994f7e1e6 192 | • Dart version 2.7.0 193 | 194 | 195 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 196 | • Android SDK at /Users/matthew/Library/Android/sdk 197 | • Android NDK location not configured (optional; useful for native profiling support) 198 | • Platform android-29, build-tools 28.0.3 199 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 200 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 201 | • All Android licenses accepted. 202 | 203 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 204 | • Xcode at /Applications/Xcode.app/Contents/Developer 205 | • Xcode 11.3.1, Build version 11C504 206 | • CocoaPods version 1.8.4 207 | 208 | [✓] Android Studio (version 3.4) 209 | • Android Studio at /Applications/Android Studio.app/Contents 210 | • Flutter plugin version 35.2.1 211 | • Dart plugin version 183.6270 212 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 213 | 214 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 215 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 216 | • Flutter plugin version 44.0.3 217 | • Dart plugin version 193.6494.35 218 | 219 | [✓] VS Code (version 1.41.1) 220 | • VS Code at /Applications/Visual Studio Code.app/Contents 221 | • Flutter extension version 3.4.1 222 | 223 | [✓] Connected device (1 available) 224 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 225 | 226 | • No issues found! 227 | ``` 228 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_find_something/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Column( 11 | children: [ 12 | RaisedButton( 13 | onPressed: () => print("rasied button was clicked"), 14 | child: Text("we will find this by searching for a type"), 15 | ), 16 | Text( 17 | "we will find this by searching for text", 18 | ), 19 | Text( 20 | "will will find this by searching for a key", 21 | key: Key("mykey"), 22 | ), 23 | ], 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_find_something/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_driver: 15 | sdk: flutter 16 | test: any 17 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_find_something/test_driver/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:ftc/main.dart' as app; 3 | 4 | void main() { 5 | // Add the following to leverage flutter_driver 6 | enableFlutterDriverExtension(); 7 | 8 | app.main(); 9 | } 10 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_find_something/test_driver/example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | FlutterDriver driver; 6 | 7 | setUpAll(() async { 8 | driver = await FlutterDriver.connect(); 9 | }); 10 | 11 | tearDownAll(() async { 12 | if (driver != null) { 13 | await driver.close(); 14 | } 15 | }); 16 | 17 | test("let's find a widget by its type", () async { 18 | // There is a RaisedButton in the main.dart file 19 | SerializableFinder raisedButtonFinder = find.byType("RaisedButton"); 20 | 21 | await driver.waitFor(raisedButtonFinder); 22 | await driver.tap(raisedButtonFinder); 23 | }); 24 | 25 | test("let's find a specific string of text", () async { 26 | // This is the exact string of text found in a Text widget in the main.dart file 27 | SerializableFinder textFinder = 28 | find.text("we will find this by searching for text"); 29 | 30 | await driver.waitFor(textFinder); 31 | }); 32 | 33 | test("let's find a widget by its key", () async { 34 | // There is a widget with its key defined as `Key("mykey")` in the main.dart file 35 | SerializableFinder keyFinder = find.byValueKey("mykey"); 36 | 37 | await driver.waitFor(keyFinder); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_pop_dialog/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (Driver) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test/ -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_pop_dialog/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I dismiss a [Dialog](https://api.flutter.dev/flutter/material/Dialog-class.html) that doesn't have an explicit widget to do so? 4 | 5 | # Answer 6 | 7 | You may find yourself creating a Dialog that relies on a user pressing on the [ModalBarrier](https://api.flutter.dev/flutter/widgets/ModalBarrier-class.html) to dismiss it (as opposed to them clicking on an explicit widget like a button). 8 | 9 | In this situation, we can use the [Navigator](https://api.flutter.dev/flutter/dart-html/Navigator-class.html) to pop the Dialog from the stack (i.e. dismiss the Dialog). 10 | 11 | To do so, we can to take advantage of the [DataHandler](https://api.flutter.dev/flutter/flutter_driver_extension/DataHandler.html) found in the [enableFlutterDriverExtension](https://api.flutter.dev/flutter/flutter_driver_extension/enableFlutterDriverExtension.html) function. 12 | 13 | ## 1) Create a flutter application. 14 | 15 | ```sh 16 | flutter create ftc 17 | cd ftc 18 | ``` 19 | 20 | ## 2) Add [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) and [test](https://pub.dev/packages/test) to your `pubspec.yaml` file. 21 | 22 | ```yaml 23 | dev_dependencies: 24 | flutter_driver: 25 | sdk: flutter 26 | test: any 27 | ``` 28 | 29 | Then get the dependencies by running: 30 | 31 | ```sh 32 | flutter packages get 33 | ``` 34 | 35 | ## 3) Replace the `lib/main.dart` file 36 | 37 | Replace the `lib/main.dart` file with the following: 38 | 39 | ```dart 40 | import 'package:flutter/material.dart'; 41 | 42 | void main() => runApp(MyApp()); 43 | 44 | GlobalKey appNavigatorKey = GlobalKey(); 45 | 46 | class MyApp extends StatelessWidget { 47 | @override 48 | Widget build(BuildContext context) { 49 | return MaterialApp( 50 | // Note: we are adding a navigatorKey here 51 | navigatorKey: appNavigatorKey, 52 | home: Scaffold( 53 | body: Body(), 54 | ), 55 | ); 56 | } 57 | } 58 | 59 | class Body extends StatelessWidget { 60 | @override 61 | Widget build(BuildContext context) { 62 | return Center( 63 | child: RaisedButton( 64 | child: Text("Open Dialog"), 65 | onPressed: () { 66 | showDialog( 67 | context: context, 68 | builder: (BuildContext _context) { 69 | return Dialog( 70 | child: SizedBox( 71 | height: 100, 72 | child: Center( 73 | child: Text("This is a Dialog"), 74 | ), 75 | ), 76 | ); 77 | }, 78 | ); 79 | }, 80 | ), 81 | ); 82 | } 83 | } 84 | ``` 85 | 86 | ## 4) Create a `test_driver` directory. 87 | 88 | ```sh 89 | mkdir test_driver 90 | cd test_driver 91 | ``` 92 | 93 | ## 5) Add an `example.dart` file. 94 | 95 | Create a `test_driver/example.dart` file, and add the following to it: 96 | 97 | ```dart 98 | import 'package:flutter_driver/driver_extension.dart'; 99 | import 'package:ftc/main.dart' as app; 100 | 101 | Future dataHandler(String message) async { 102 | // We are using the data handler to execute a pop side-effect 103 | if (message == "pop") { 104 | app.appNavigatorKey.currentState.pop(); 105 | } 106 | 107 | return null; 108 | } 109 | 110 | void main() { 111 | enableFlutterDriverExtension( 112 | // we are adding the dataHandler here 113 | handler: dataHandler, 114 | ); 115 | 116 | app.main(); 117 | } 118 | ``` 119 | 120 | ## 6) Add an `example_test.dart` file. 121 | 122 | Create a `test_driver/example_test.dart` file, and add the following to it: 123 | 124 | ```dart 125 | import 'package:flutter_driver/flutter_driver.dart'; 126 | import 'package:test/test.dart'; 127 | 128 | void main() { 129 | FlutterDriver driver; 130 | 131 | setUpAll(() async { 132 | driver = await FlutterDriver.connect(); 133 | }); 134 | 135 | tearDownAll(() async { 136 | if (driver != null) { 137 | await driver.close(); 138 | } 139 | }); 140 | 141 | test("let's open a dialog and then close it", () async { 142 | SerializableFinder raisedButtonFinder = find.byType("RaisedButton"); 143 | await driver.waitFor(raisedButtonFinder); 144 | // tapping the button opens the Dialog 145 | await driver.tap(raisedButtonFinder); 146 | 147 | // make sure the the Dialog is open 148 | SerializableFinder dialogFinder = find.byType("Dialog"); 149 | await driver.waitFor(dialogFinder); 150 | 151 | // now, let's close the dialog using the dataHandler we defined in 152 | // our [enableFlutterDriverExtension] function. 153 | await driver.requestData("pop"); 154 | 155 | // finally, let's wait to make sure the Dialog is gone 156 | await driver.waitForAbsent(dialogFinder); 157 | }); 158 | } 159 | ``` 160 | 161 | ## 7) Run the flutter driver tests 162 | 163 | ```sh 164 | flutter driver -t test_driver/example.dart 165 | ``` 166 | 167 | # Run tests from example code in cookbook itself 168 | 169 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 170 | 171 | ```sh 172 | flutter create . 173 | flutter packages get 174 | flutter driver -t test_driver/example.dart 175 | ``` 176 | 177 | # Outputs when recipe was made / updated 178 | 179 | ## Output of running flutter driver tests 180 | 181 | ```sh 182 | $ flutter driver -t test_driver/example.dart 183 | Using device iPhone 8. 184 | Starting application: test_driver/example.dart 185 | Running Xcode build... 186 | ├─Assembling Flutter resources... 2.6s 187 | └─Compiling, linking and signing... 2.9s 188 | Xcode build done. 6.6s 189 | flutter: Observatory listening on http://127.0.0.1:51796/uIFIjnNgNMs=/ 190 | 00:00 +0: (setUpAll) 191 | 192 | [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:51796/uIFIjnNgNMs=/ 193 | [trace] FlutterDriver: Isolate found with number: 491196511285567 194 | [trace] FlutterDriver: Isolate is paused at start. 195 | [trace] FlutterDriver: Attempting to resume isolate 196 | [trace] FlutterDriver: Waiting for service extension 197 | [info ] FlutterDriver: Connected to Flutter application. 198 | 00:00 +0: let's open a dialog and then close it 199 | 200 | 00:00 +1: (tearDownAll) 201 | 202 | 00:00 +1: All tests passed! 203 | 204 | Stopping application instance. 205 | ``` 206 | 207 | ## Output of running `flutter doctor -v` 208 | 209 | ```sh 210 | $ flutter doctor -v 211 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 212 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 213 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 214 | • Engine revision 2994f7e1e6 215 | • Dart version 2.7.0 216 | 217 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 218 | • Android SDK at /Users/matthew/Library/Android/sdk 219 | • Android NDK location not configured (optional; useful for native profiling support) 220 | • Platform android-29, build-tools 28.0.3 221 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 222 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 223 | • All Android licenses accepted. 224 | 225 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 226 | • Xcode at /Applications/Xcode.app/Contents/Developer 227 | • Xcode 11.3.1, Build version 11C504 228 | • CocoaPods version 1.8.4 229 | 230 | [✓] Android Studio (version 3.4) 231 | • Android Studio at /Applications/Android Studio.app/Contents 232 | • Flutter plugin version 35.2.1 233 | • Dart plugin version 183.6270 234 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 235 | 236 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 237 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 238 | • Flutter plugin version 44.0.3 239 | • Dart plugin version 193.6494.35 240 | 241 | [✓] VS Code (version 1.41.1) 242 | • VS Code at /Applications/Visual Studio Code.app/Contents 243 | • Flutter extension version 3.4.1 244 | 245 | [✓] Connected device (1 available) 246 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 247 | 248 | • No issues found! 249 | ``` 250 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_pop_dialog/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | GlobalKey appNavigatorKey = GlobalKey(); 6 | 7 | class MyApp extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | // Note: we are adding a navigatorKey here 12 | navigatorKey: appNavigatorKey, 13 | home: Scaffold( 14 | body: Body(), 15 | ), 16 | ); 17 | } 18 | } 19 | 20 | class Body extends StatelessWidget { 21 | @override 22 | Widget build(BuildContext context) { 23 | return Center( 24 | child: RaisedButton( 25 | child: Text("Open Dialog"), 26 | onPressed: () { 27 | showDialog( 28 | context: context, 29 | builder: (BuildContext _context) { 30 | return Dialog( 31 | child: SizedBox( 32 | height: 100, 33 | child: Center( 34 | child: Text("This is a Dialog"), 35 | ), 36 | ), 37 | ); 38 | }, 39 | ); 40 | }, 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_pop_dialog/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_driver: 15 | sdk: flutter 16 | test: any 17 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_pop_dialog/test_driver/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:ftc/main.dart' as app; 3 | 4 | Future dataHandler(String message) async { 5 | // We are using the data handler to execute a pop side-effect 6 | if (message == "pop") { 7 | app.appNavigatorKey.currentState.pop(); 8 | } 9 | 10 | return null; 11 | } 12 | 13 | void main() { 14 | enableFlutterDriverExtension( 15 | // we are adding the dataHandler here 16 | handler: dataHandler, 17 | ); 18 | 19 | app.main(); 20 | } 21 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_pop_dialog/test_driver/example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | FlutterDriver driver; 6 | 7 | setUpAll(() async { 8 | driver = await FlutterDriver.connect(); 9 | }); 10 | 11 | tearDownAll(() async { 12 | if (driver != null) { 13 | await driver.close(); 14 | } 15 | }); 16 | 17 | test("let's open a dialog and then close it", () async { 18 | SerializableFinder raisedButtonFinder = find.byType("RaisedButton"); 19 | await driver.waitFor(raisedButtonFinder); 20 | // tapping the button opens the Dialog 21 | await driver.tap(raisedButtonFinder); 22 | 23 | // make sure the the Dialog is open 24 | SerializableFinder dialogFinder = find.byType("Dialog"); 25 | await driver.waitFor(dialogFinder); 26 | 27 | // now, let's close the dialog using the dataHandler we defined in 28 | // our [enableFlutterDriverExtension] function. 29 | await driver.requestData("pop"); 30 | 31 | // finally, let's wait to make sure the Dialog is gone 32 | await driver.waitForAbsent(dialogFinder); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_flutter_driver_test/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (Driver) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test/ -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_flutter_driver_test/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I run a [flutter driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) test? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Add [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) and [test](https://pub.dev/packages/test) to your `pubspec.yaml` file. 15 | 16 | ```yaml 17 | dev_dependencies: 18 | flutter_driver: 19 | sdk: flutter 20 | test: any 21 | ``` 22 | 23 | Then get the dependencies by running: 24 | 25 | ```sh 26 | flutter packages get 27 | ``` 28 | 29 | ## 3) Replace the `lib/main.dart` file 30 | 31 | Replace the `lib/main.dart` file with the following: 32 | 33 | ```dart 34 | import 'package:flutter/material.dart'; 35 | 36 | void main() => runApp(MyApp()); 37 | 38 | class MyApp extends StatelessWidget { 39 | @override 40 | Widget build(BuildContext context) { 41 | return MaterialApp( 42 | home: Scaffold( 43 | body: Center( 44 | child: Text("Hello world"), 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | ``` 51 | 52 | ## 4) Create a `test_driver` directory. 53 | 54 | ```sh 55 | mkdir test_driver 56 | cd test_driver 57 | ``` 58 | 59 | ## 5) Add an `example.dart` file. 60 | 61 | Create a `test_driver/example.dart` file, and add the following to it: 62 | 63 | ```dart 64 | import 'package:flutter_driver/driver_extension.dart'; 65 | import 'package:ftc/main.dart' as app; 66 | 67 | void main() { 68 | // Add the following to leverage flutter_driver 69 | enableFlutterDriverExtension(); 70 | 71 | app.main(); 72 | } 73 | ``` 74 | 75 | [Reference](https://flutter.dev/docs/cookbook/testing/integration/introduction#4-instrument-the-app) 76 | 77 | ## 6) Add an `example_test.dart` file. 78 | 79 | Create a `test_driver/example_test.dart` file, and add the following to it: 80 | 81 | ```dart 82 | import 'package:flutter_driver/flutter_driver.dart'; 83 | import 'package:test/test.dart'; 84 | 85 | void main() { 86 | FlutterDriver driver; 87 | 88 | setUpAll(() async { 89 | driver = await FlutterDriver.connect(); 90 | }); 91 | 92 | tearDownAll(() async { 93 | if (driver != null) { 94 | await driver.close(); 95 | } 96 | }); 97 | 98 | test("'Hello world' text exists", () async { 99 | SerializableFinder helloWorldTextFinder = find.text("Hello world"); 100 | 101 | await driver.waitFor(helloWorldTextFinder); 102 | }); 103 | } 104 | ``` 105 | 106 | ## 7) Run the flutter driver tests 107 | 108 | ```sh 109 | flutter driver -t test_driver/example.dart 110 | ``` 111 | 112 | # Run tests from example code in cookbook itself 113 | 114 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 115 | 116 | ```sh 117 | flutter create . 118 | flutter packages get 119 | flutter driver -t test_driver/example.dart 120 | ``` 121 | 122 | # Outputs when recipe was made / updated 123 | 124 | ## Output of running flutter driver tests 125 | 126 | ```sh 127 | $ flutter driver -t test_driver/example.dart 128 | Using device iPhone 8. 129 | Starting application: test_driver/example.dart 130 | Running Xcode build... 131 | ├─Assembling Flutter resources... 7.9s 132 | └─Compiling, linking and signing... 4.1s 133 | Xcode build done. 13.2s 134 | flutter: Observatory listening on http://127.0.0.1:60787/uPVFLEOkdqo=/ 135 | 00:00 +0: (setUpAll) 136 | 137 | [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:60787/uPVFLEOkdqo=/ 138 | [trace] FlutterDriver: Isolate found with number: 4366875479017451 139 | [trace] FlutterDriver: Isolate is paused at start. 140 | [trace] FlutterDriver: Attempting to resume isolate 141 | [trace] FlutterDriver: Waiting for service extension 142 | [info ] FlutterDriver: Connected to Flutter application. 143 | 00:00 +0: 'Hello world' text exists 144 | 145 | 00:00 +1: (tearDownAll) 146 | 147 | 00:00 +1: All tests passed! 148 | 149 | Stopping application instance. 150 | ``` 151 | 152 | ## Output of running `flutter doctor -v` 153 | 154 | ```sh 155 | $ flutter doctor -v 156 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 157 | 19C57, locale en-US) 158 | • Flutter version 1.12.13+hotfix.5 at 159 | /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 160 | • Framework revision 27321ebbad (7 weeks ago), 2019-12-10 18:15:01 161 | -0800 162 | • Engine revision 2994f7e1e6 163 | • Dart version 2.7.0 164 | 165 | [✓] Android toolchain - develop for Android devices (Android SDK 166 | version 28.0.3) 167 | • Android SDK at /Users/matthew/Library/Android/sdk 168 | • Android NDK location not configured (optional; useful for native 169 | profiling support) 170 | • Platform android-29, build-tools 28.0.3 171 | • Java binary at: /Applications/Android 172 | Studio.app/Contents/jre/jdk/Contents/Home/bin/java 173 | • Java version OpenJDK Runtime Environment (build 174 | 1.8.0_152-release-1343-b01) 175 | • All Android licenses accepted. 176 | 177 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 178 | • Xcode at /Applications/Xcode.app/Contents/Developer 179 | • Xcode 11.3.1, Build version 11C504 180 | • CocoaPods version 1.8.4 181 | 182 | [✓] Android Studio (version 3.4) 183 | • Android Studio at /Applications/Android Studio.app/Contents 184 | • Flutter plugin version 35.2.1 185 | • Dart plugin version 183.6270 186 | • Java version OpenJDK Runtime Environment (build 187 | 1.8.0_152-release-1343-b01) 188 | 189 | [✓] IntelliJ IDEA Community Edition (version 2019.3.2) 190 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 191 | • Flutter plugin version 42.1.4 192 | • Dart plugin version 193.6015.9 193 | 194 | [✓] VS Code (version 1.40.1) 195 | • VS Code at /Applications/Visual Studio Code.app/Contents 196 | • Flutter extension version 3.4.1 197 | 198 | [✓] Connected device (1 available) 199 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • 200 | com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 201 | 202 | • No issues found! 203 | ``` 204 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_flutter_driver_test/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Center( 11 | child: Text("Hello world"), 12 | ), 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_flutter_driver_test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_driver: 14 | sdk: flutter 15 | test: any 16 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_flutter_driver_test/test_driver/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:ftc/main.dart' as app; 3 | 4 | void main() { 5 | // Add the following to leverage flutter_driver 6 | enableFlutterDriverExtension(); 7 | 8 | app.main(); 9 | } 10 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_flutter_driver_test/test_driver/example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | FlutterDriver driver; 6 | 7 | setUpAll(() async { 8 | driver = await FlutterDriver.connect(); 9 | }); 10 | 11 | tearDownAll(() async { 12 | if (driver != null) { 13 | await driver.close(); 14 | } 15 | }); 16 | 17 | test("'Hello world' text exists", () async { 18 | SerializableFinder helloWorldTextFinder = find.text("Hello world"); 19 | 20 | await driver.waitFor(helloWorldTextFinder); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (Driver) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test/ -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I run a script inside of a test? 4 | 5 | # Answer 6 | 7 | Use [Process.run](https://api.dart.dev/stable/2.7.1/dart-io/Process/run.html) 8 | 9 | ## 1) Create a flutter application. 10 | 11 | ```sh 12 | flutter create ftc 13 | cd ftc 14 | ``` 15 | 16 | ## 2) Add [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) and [test](https://pub.dev/packages/test) to your `pubspec.yaml` file. 17 | 18 | ```yaml 19 | dev_dependencies: 20 | flutter_driver: 21 | sdk: flutter 22 | test: any 23 | ``` 24 | 25 | Then get the dependencies by running: 26 | 27 | ```sh 28 | flutter packages get 29 | ``` 30 | 31 | ## 3) Replace the `lib/main.dart` file 32 | 33 | Replace the `lib/main.dart` file with the following: 34 | 35 | ```dart 36 | import 'package:flutter/material.dart'; 37 | 38 | void main() => runApp(MyApp()); 39 | 40 | class MyApp extends StatelessWidget { 41 | @override 42 | Widget build(BuildContext context) { 43 | return MaterialApp( 44 | home: Scaffold( 45 | body: Center( 46 | child: Text("A script will be run during the test"), 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | ``` 53 | 54 | ## 4) Create a `test_driver` directory. 55 | 56 | ```sh 57 | mkdir test_driver 58 | cd test_driver 59 | ``` 60 | 61 | ## 5) Add an `example.dart` file. 62 | 63 | Create a `test_driver/example.dart` file, and add the following to it: 64 | 65 | ```dart 66 | import 'package:flutter_driver/driver_extension.dart'; 67 | import 'package:ftc/main.dart' as app; 68 | 69 | void main() { 70 | // Add the following to leverage flutter_driver 71 | enableFlutterDriverExtension(); 72 | 73 | app.main(); 74 | } 75 | ``` 76 | 77 | ## 6) Add an `example_test.dart` file. 78 | 79 | Create a `test_driver/example_test.dart` file, and add the following to it: 80 | 81 | ```dart 82 | import 'package:flutter_driver/flutter_driver.dart'; 83 | import 'package:test/test.dart'; 84 | import 'dart:io'; 85 | 86 | void main() { 87 | FlutterDriver driver; 88 | 89 | setUpAll(() async { 90 | driver = await FlutterDriver.connect(); 91 | }); 92 | 93 | tearDownAll(() async { 94 | if (driver != null) { 95 | await driver.close(); 96 | } 97 | }); 98 | 99 | test("How do I run a script inside of a test?", () async { 100 | ProcessResult result = await Process.run('./echo.sh', ['1']); 101 | 102 | // Note: echo appends a newline to the result 103 | expect(result.stdout, '1\n'); 104 | }); 105 | } 106 | ``` 107 | 108 | ## 7) Add an `echo.sh` script 109 | 110 | Create a file called `echo.sh` and add the following to it: 111 | 112 | ```sh 113 | #!/bin/bash 114 | 115 | echo "$1" 116 | ``` 117 | 118 | Then run: 119 | 120 | ```sh 121 | chmod +x echo.sh 122 | ``` 123 | 124 | ## 8) Run the flutter driver tests 125 | 126 | ```sh 127 | flutter driver -t test_driver/example.dart 128 | ``` 129 | 130 | # Run tests from example code in cookbook itself 131 | 132 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 133 | 134 | ```sh 135 | flutter create . 136 | flutter packages get 137 | flutter driver -t test_driver/example.dart 138 | ``` 139 | 140 | # Outputs when recipe was made / updated 141 | 142 | ## Output of running flutter driver tests 143 | 144 | ```sh 145 | $ flutter driver -t test_driver/example.dart 146 | Using device iPhone 8. 147 | Starting application: test_driver/example.dart 148 | Running Xcode build... 149 | ├─Assembling Flutter resources... 2.5s 150 | └─Compiling, linking and signing... ⣷^R 3.0s 151 | Xcode build done. 6.7s 152 | flutter: Observatory listening on http://127.0.0.1:63926/-gi_tJzC8oY=/ 153 | 00:00 +0: (setUpAll) 154 | 155 | [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:63926/-gi_tJzC8oY=/ 156 | [trace] FlutterDriver: Isolate found with number: 3688768521236175 157 | [trace] FlutterDriver: Isolate is paused at start. 158 | [trace] FlutterDriver: Attempting to resume isolate 159 | [trace] FlutterDriver: Waiting for service extension 160 | [info ] FlutterDriver: Connected to Flutter application. 161 | 00:00 +0: How do I run a script inside of a test? 162 | 163 | 00:00 +1: (tearDownAll) 164 | 165 | 00:00 +1: All tests passed! 166 | ``` 167 | 168 | ## Output of running `flutter doctor -v` 169 | 170 | ```sh 171 | $ flutter doctor -v 172 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 173 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 174 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 175 | • Engine revision 2994f7e1e6 176 | • Dart version 2.7.0 177 | 178 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 179 | • Android SDK at /Users/matthew/Library/Android/sdk 180 | • Android NDK location not configured (optional; useful for native profiling support) 181 | • Platform android-29, build-tools 28.0.3 182 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 183 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 184 | • All Android licenses accepted. 185 | 186 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 187 | • Xcode at /Applications/Xcode.app/Contents/Developer 188 | • Xcode 11.3.1, Build version 11C504 189 | • CocoaPods version 1.8.4 190 | 191 | [✓] Android Studio (version 3.4) 192 | • Android Studio at /Applications/Android Studio.app/Contents 193 | • Flutter plugin version 35.2.1 194 | • Dart plugin version 183.6270 195 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 196 | 197 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 198 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 199 | • Flutter plugin version 44.0.3 200 | • Dart plugin version 193.6494.35 201 | 202 | [✓] VS Code (version 1.41.1) 203 | • VS Code at /Applications/Visual Studio Code.app/Contents 204 | • Flutter extension version 3.4.1 205 | 206 | [✓] Connected device (1 available) 207 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 208 | 209 | • No issues found! 210 | ``` 211 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "$1" 4 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Center( 11 | child: Text("A script will be run during the test"), 12 | ), 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_driver: 14 | sdk: flutter 15 | test: any 16 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/test_driver/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:ftc/main.dart' as app; 3 | 4 | void main() { 5 | // Add the following to leverage flutter_driver 6 | enableFlutterDriverExtension(); 7 | 8 | app.main(); 9 | } 10 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_a_script/test_driver/example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | import 'dart:io'; 4 | 5 | void main() { 6 | FlutterDriver driver; 7 | 8 | setUpAll(() async { 9 | driver = await FlutterDriver.connect(); 10 | }); 11 | 12 | tearDownAll(() async { 13 | if (driver != null) { 14 | await driver.close(); 15 | } 16 | }); 17 | 18 | test("How do I run a script inside of a test?", () async { 19 | ProcessResult result = await Process.run('./echo.sh', ['1']); 20 | 21 | // Note: echo appends a newline to the result 22 | expect(result.stdout, '1\n'); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (Driver) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test/ -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I run multiple test files without restarting the app? 4 | 5 | # Answer 6 | 7 | We are going to start an app, record its uri, then run our test suites referencing an existing app at the uri. 8 | 9 | ## 1) Create a flutter application. 10 | 11 | ```sh 12 | flutter create ftc 13 | cd ftc 14 | ``` 15 | 16 | ## 2) Add [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) and [test](https://pub.dev/packages/test) to your `pubspec.yaml` file. 17 | 18 | ```yaml 19 | dev_dependencies: 20 | flutter_driver: 21 | sdk: flutter 22 | test: any 23 | ``` 24 | 25 | Then get the dependencies by running: 26 | 27 | ```sh 28 | flutter packages get 29 | ``` 30 | 31 | ## 3) Replace the `lib/main.dart` file 32 | 33 | Replace the `lib/main.dart` file with the following: 34 | 35 | ```dart 36 | import 'package:flutter/material.dart'; 37 | 38 | void main() => runApp(MyApp()); 39 | 40 | class MyApp extends StatelessWidget { 41 | @override 42 | Widget build(BuildContext context) { 43 | return MaterialApp( 44 | home: Scaffold( 45 | body: Column(children: [ 46 | Text("Suite 1 will check this"), 47 | Text("Suite 2 will check this"), 48 | ]), 49 | ), 50 | ); 51 | } 52 | } 53 | ``` 54 | 55 | ## 4) Create a `test_driver` directory. 56 | 57 | ```sh 58 | mkdir test_driver 59 | cd test_driver 60 | ``` 61 | 62 | ## 5) Add a `start_app.dart` file. 63 | 64 | Create a `test_driver/start_app.dart` file, and add the following to it: 65 | 66 | ```dart 67 | import 'package:flutter_driver/driver_extension.dart'; 68 | import 'package:ftc/main.dart' as app; 69 | 70 | void main() { 71 | enableFlutterDriverExtension(); 72 | 73 | app.main(); 74 | } 75 | ``` 76 | 77 | ## 6) Add a `suite1.dart` file and a `suite1_test.dart` file 78 | 79 | Create a `test_driver/suite1.dart` file, and add the following to it: 80 | 81 | ```dart 82 | // Note: this is intentionally blank, 83 | // but the existence of this file is still needed 84 | // for flutter drive to run the tests 85 | ``` 86 | 87 | Then create a `test_driver/suite1_test.dart` file, and add the following to it: 88 | 89 | ```dart 90 | import 'package:flutter_driver/flutter_driver.dart'; 91 | import 'package:test/test.dart'; 92 | 93 | void main() { 94 | FlutterDriver driver; 95 | 96 | setUpAll(() async { 97 | driver = await FlutterDriver.connect(); 98 | }); 99 | 100 | tearDownAll(() async { 101 | if (driver != null) { 102 | await driver.close(); 103 | } 104 | }); 105 | 106 | test("Suite 1", () async { 107 | SerializableFinder textFinder = find.text("Suite 1 will check this"); 108 | 109 | await driver.waitFor(textFinder); 110 | }); 111 | } 112 | ``` 113 | 114 | ## 7) Add a `suite2.dart` file and a `suite2_test.dart` file 115 | 116 | Create a `test_driver/suite2.dart` file, and add the following to it: 117 | 118 | ```dart 119 | // Note: this is intentionally blank, 120 | // but the existence of this file is still needed 121 | // for flutter drive to run the tests 122 | ``` 123 | 124 | Then create a `test_driver/suite2_test.dart` file, and add the following to it: 125 | 126 | ```dart 127 | import 'package:flutter_driver/flutter_driver.dart'; 128 | import 'package:test/test.dart'; 129 | 130 | void main() { 131 | FlutterDriver driver; 132 | 133 | setUpAll(() async { 134 | driver = await FlutterDriver.connect(); 135 | }); 136 | 137 | tearDownAll(() async { 138 | if (driver != null) { 139 | await driver.close(); 140 | } 141 | }); 142 | 143 | test("Suite 2", () async { 144 | SerializableFinder textFinder = find.text("Suite 2 will check this"); 145 | 146 | await driver.waitFor(textFinder); 147 | }); 148 | } 149 | ``` 150 | 151 | ## 8) Add a `run_all_tests.sh` file 152 | 153 | Create a `run_all_tests.sh` file and add the following to it: 154 | 155 | ```sh 156 | #!/bin/bash 157 | 158 | # start the app and write the uri to a file 159 | flutter run --target=test_driver/start_app.dart --vmservice-out-file="test_driver/uri.txt" --start-paused --no-resident 160 | 161 | # run the test suites using the uri in the aforementioned file 162 | flutter driver --target=test_driver/suite1.dart --use-existing-app="$(cat test_driver/uri.txt)" 163 | flutter driver --target=test_driver/suite2.dart --use-existing-app="$(cat test_driver/uri.txt)" 164 | ``` 165 | 166 | Then make it executable by running 167 | 168 | ```sh 169 | chmod +x run_all_tests.sh 170 | ``` 171 | 172 | ## 8) Run the flutter driver tests 173 | 174 | ```sh 175 | ./run_all_tests.sh 176 | ``` 177 | 178 | # Run tests from example code in cookbook itself 179 | 180 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 181 | 182 | ```sh 183 | flutter create . 184 | flutter packages get 185 | ./run_all_tests.sh 186 | ``` 187 | 188 | # Outputs when recipe was made / updated 189 | 190 | ## Output of running flutter driver tests 191 | 192 | ```sh 193 | $ ./run_all_tests.sh 194 | Launching test_driver/start_app.dart on iPhone 8 in debug mode... 195 | Running Xcode build... 196 | ├─Assembling Flutter resources... 2.6s 197 | └─Compiling, linking and signing... 2.9s 198 | Xcode build done. 6.7s 199 | Syncing files to device iPhone 8... 200 | 4,776ms (!) 201 | Using device iPhone 8. 202 | Will connect to already running application instance. 203 | 00:00 +0: (setUpAll) 204 | 205 | [info ] FlutterDriver: Connecting to Flutter application at ws://127.0.0.1:56503/87Z5gLVG5E8=/ws 206 | [trace] FlutterDriver: Isolate found with number: 1576235581101003 207 | [trace] FlutterDriver: Isolate is paused at start. 208 | [trace] FlutterDriver: Attempting to resume isolate 209 | [trace] FlutterDriver: Waiting for service extension 210 | [info ] FlutterDriver: Connected to Flutter application. 211 | 00:00 +0: Suite 1 212 | 213 | 00:00 +1: (tearDownAll) 214 | 215 | 00:00 +1: All tests passed! 216 | 217 | Leaving the application running. 218 | Using device iPhone 8. 219 | Will connect to already running application instance. 220 | 00:00 +0: (setUpAll) 221 | 222 | [info ] FlutterDriver: Connecting to Flutter application at ws://127.0.0.1:56503/87Z5gLVG5E8=/ws 223 | [trace] FlutterDriver: Isolate found with number: 1576235581101003 224 | [trace] FlutterDriver: Isolate is not paused. Assuming application is ready. 225 | [info ] FlutterDriver: Connected to Flutter application. 226 | 00:00 +0: Suite 2 227 | 228 | 00:00 +1: (tearDownAll) 229 | 230 | 00:00 +1: All tests passed! 231 | 232 | Leaving the application running. 233 | ``` 234 | 235 | ## Output of running `flutter doctor -v` 236 | 237 | ```sh 238 | $ flutter doctor -v 239 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 240 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 241 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 242 | • Engine revision 2994f7e1e6 243 | • Dart version 2.7.0 244 | 245 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 246 | • Android SDK at /Users/matthew/Library/Android/sdk 247 | • Android NDK location not configured (optional; useful for native profiling support) 248 | • Platform android-29, build-tools 28.0.3 249 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 250 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 251 | • All Android licenses accepted. 252 | 253 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 254 | • Xcode at /Applications/Xcode.app/Contents/Developer 255 | • Xcode 11.3.1, Build version 11C504 256 | • CocoaPods version 1.8.4 257 | 258 | [✓] Android Studio (version 3.4) 259 | • Android Studio at /Applications/Android Studio.app/Contents 260 | • Flutter plugin version 35.2.1 261 | • Dart plugin version 183.6270 262 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 263 | 264 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 265 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 266 | • Flutter plugin version 44.0.3 267 | • Dart plugin version 193.6494.35 268 | 269 | [✓] VS Code (version 1.41.1) 270 | • VS Code at /Applications/Visual Studio Code.app/Contents 271 | • Flutter extension version 3.4.1 272 | 273 | [✓] Connected device (1 available) 274 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 275 | 276 | • No issues found! 277 | ``` 278 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Column(children: [ 11 | Text("Suite 1 will check this"), 12 | Text("Suite 2 will check this"), 13 | ]), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_driver: 14 | sdk: flutter 15 | test: any 16 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/run_all_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # start the app and write the uri to a file 4 | flutter run --target=test_driver/start_app.dart --vmservice-out-file="test_driver/uri.txt" --start-paused --no-resident 5 | 6 | # run the test suites using the uri in the aforementioned file 7 | flutter driver --target=test_driver/suite1.dart --use-existing-app="$(cat test_driver/uri.txt)" 8 | flutter driver --target=test_driver/suite2.dart --use-existing-app="$(cat test_driver/uri.txt)" -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/test_driver/start_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:ftc/main.dart' as app; 3 | 4 | void main() { 5 | // Add the following to leverage flutter_driver 6 | enableFlutterDriverExtension(); 7 | 8 | app.main(); 9 | } 10 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/test_driver/suite1.dart: -------------------------------------------------------------------------------- 1 | // Note: this is intentionally blank, 2 | // but the existence of this file is still needed 3 | // for flutter drive to run the tests 4 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/test_driver/suite1_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | FlutterDriver driver; 6 | 7 | setUpAll(() async { 8 | driver = await FlutterDriver.connect(); 9 | }); 10 | 11 | tearDownAll(() async { 12 | if (driver != null) { 13 | await driver.close(); 14 | } 15 | }); 16 | 17 | test("Suite 1", () async { 18 | SerializableFinder textFinder = find.text("Suite 1 will check this"); 19 | 20 | await driver.waitFor(textFinder); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/test_driver/suite2.dart: -------------------------------------------------------------------------------- 1 | // Note: this is intentionally blank, 2 | // but the existence of this file is still needed 3 | // for flutter drive to run the tests 4 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_run_multiple_test_files_without_restarting_app/test_driver/suite2_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | FlutterDriver driver; 6 | 7 | setUpAll(() async { 8 | driver = await FlutterDriver.connect(); 9 | }); 10 | 11 | tearDownAll(() async { 12 | if (driver != null) { 13 | await driver.close(); 14 | } 15 | }); 16 | 17 | test("Suite 2", () async { 18 | SerializableFinder textFinder = find.text("Suite 2 will check this"); 19 | 20 | await driver.waitFor(textFinder); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (Driver) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test/ -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I take a screenshot? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Add [flutter_driver](https://api.flutter.dev/flutter/flutter_driver/flutter_driver-library.html) and [test](https://pub.dev/packages/test) to your `pubspec.yaml` file. 15 | 16 | ```yaml 17 | dev_dependencies: 18 | flutter_driver: 19 | sdk: flutter 20 | test: any 21 | ``` 22 | 23 | Then get the dependencies by running: 24 | 25 | ```sh 26 | flutter packages get 27 | ``` 28 | 29 | ## 3) Replace the `lib/main.dart` file 30 | 31 | Replace the `lib/main.dart` file with the following: 32 | 33 | ```dart 34 | import 'package:flutter/material.dart'; 35 | 36 | void main() => runApp(MyApp()); 37 | 38 | class MyApp extends StatelessWidget { 39 | @override 40 | Widget build(BuildContext context) { 41 | return MaterialApp( 42 | home: Scaffold( 43 | body: Center( 44 | child: Text("Take a screenshot of me"), 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | ``` 51 | 52 | ## 4) Create a `test_driver` directory. 53 | 54 | ```sh 55 | mkdir test_driver 56 | cd test_driver 57 | ``` 58 | 59 | ## 5) Add an `example.dart` file. 60 | 61 | Create a `test_driver/example.dart` file, and add the following to it: 62 | 63 | ```dart 64 | import 'package:flutter_driver/driver_extension.dart'; 65 | import 'package:ftc/main.dart' as app; 66 | 67 | void main() { 68 | // Add the following to leverage flutter_driver 69 | enableFlutterDriverExtension(); 70 | 71 | app.main(); 72 | } 73 | ``` 74 | 75 | ## 6) Add an `example_test.dart` file. 76 | 77 | Create a `test_driver/example_test.dart` file, and add the following to it: 78 | 79 | ```dart 80 | import 'package:flutter_driver/flutter_driver.dart'; 81 | import 'package:test/test.dart'; 82 | import 'dart:io' show File; 83 | 84 | void main() { 85 | FlutterDriver driver; 86 | 87 | setUpAll(() async { 88 | driver = await FlutterDriver.connect(); 89 | }); 90 | 91 | tearDownAll(() async { 92 | if (driver != null) { 93 | await driver.close(); 94 | } 95 | }); 96 | 97 | test("How do I take a screenshot?", () async { 98 | // It is good practice to call this before taking a screenshot 99 | // to ensure everything has settled 100 | await driver.waitUntilNoTransientCallbacks(); 101 | 102 | // Take the screenshot and store as a list of ints 103 | // Note: the image will be returned as a PNG 104 | final List screenshotPixels = await driver.screenshot(); 105 | 106 | // Write to a file 107 | final File screenshotFile = new File("my_screenshot.png"); 108 | await screenshotFile.writeAsBytes(screenshotPixels); 109 | }); 110 | } 111 | ``` 112 | 113 | ## 7) Run the flutter driver tests 114 | 115 | ```sh 116 | flutter driver -t test_driver/example.dart 117 | ``` 118 | 119 | # Run tests from example code in cookbook itself 120 | 121 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 122 | 123 | ```sh 124 | flutter create . 125 | flutter packages get 126 | flutter driver -t test_driver/example.dart 127 | ``` 128 | 129 | # Outputs when recipe was made / updated 130 | 131 | ## Output of running flutter driver tests 132 | 133 | ```sh 134 | $ flutter driver -t test_driver/example.dart 135 | Using device iPhone 8. 136 | Starting application: test_driver/example.dart 137 | Running Xcode build... 138 | ├─Assembling Flutter resources... 2.5s 139 | └─Compiling, linking and signing... 2.8s 140 | Xcode build done. 6.5s 141 | flutter: Observatory listening on http://127.0.0.1:57202/-lHi4AILSr8=/ 142 | 00:00 +0: (setUpAll) 143 | 144 | [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:57202/-lHi4AILSr8=/ 145 | [trace] FlutterDriver: Isolate found with number: 2332930742028219 146 | [trace] FlutterDriver: Isolate is paused at start. 147 | [trace] FlutterDriver: Attempting to resume isolate 148 | [trace] FlutterDriver: Waiting for service extension 149 | [info ] FlutterDriver: Connected to Flutter application. 150 | 00:00 +0: How do I take a screenshot? 151 | 152 | 00:02 +1: (tearDownAll) 153 | 154 | 00:02 +1: All tests passed! 155 | 156 | Stopping application instance. 157 | ``` 158 | 159 | ## Output of running `flutter doctor -v` 160 | 161 | ```sh 162 | $ flutter doctor -v 163 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 164 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 165 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 166 | • Engine revision 2994f7e1e6 167 | • Dart version 2.7.0 168 | 169 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 170 | • Android SDK at /Users/matthew/Library/Android/sdk 171 | • Android NDK location not configured (optional; useful for native profiling support) 172 | • Platform android-29, build-tools 28.0.3 173 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 174 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 175 | • All Android licenses accepted. 176 | 177 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 178 | • Xcode at /Applications/Xcode.app/Contents/Developer 179 | • Xcode 11.3.1, Build version 11C504 180 | • CocoaPods version 1.8.4 181 | 182 | [✓] Android Studio (version 3.4) 183 | • Android Studio at /Applications/Android Studio.app/Contents 184 | • Flutter plugin version 35.2.1 185 | • Dart plugin version 183.6270 186 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 187 | 188 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 189 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 190 | • Flutter plugin version 44.0.3 191 | • Dart plugin version 193.6494.35 192 | 193 | [✓] VS Code (version 1.41.1) 194 | • VS Code at /Applications/Visual Studio Code.app/Contents 195 | • Flutter extension version 3.4.1 196 | 197 | [✓] Connected device (1 available) 198 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 199 | 200 | • No issues found! 201 | ``` 202 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Center( 11 | child: Text("Take a screenshot of me"), 12 | ), 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/my_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gadfly361/flutter_test_cookbook/d40b6e3253323a1272b952a2b9a01d203ab0d637/recipes/flutter_driver/how_do_i_take_a_screenshot/my_screenshot.png -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_driver: 14 | sdk: flutter 15 | test: any 16 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/test_driver/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:ftc/main.dart' as app; 3 | 4 | void main() { 5 | // Add the following to leverage flutter_driver 6 | enableFlutterDriverExtension(); 7 | 8 | app.main(); 9 | } 10 | -------------------------------------------------------------------------------- /recipes/flutter_driver/how_do_i_take_a_screenshot/test_driver/example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/flutter_driver.dart'; 2 | import 'package:test/test.dart'; 3 | import 'dart:io' show File; 4 | 5 | void main() { 6 | FlutterDriver driver; 7 | 8 | setUpAll(() async { 9 | driver = await FlutterDriver.connect(); 10 | }); 11 | 12 | tearDownAll(() async { 13 | if (driver != null) { 14 | await driver.close(); 15 | } 16 | }); 17 | 18 | test("How do I take a screenshot?", () async { 19 | // It is good practice to call this before taking a screenshot 20 | // to ensure everything has settled 21 | await driver.waitUntilNoTransientCallbacks(); 22 | 23 | // Take the screenshot and store as a list of ints 24 | // Note: the image will be returned as a PNG 25 | final List screenshotPixels = await driver.screenshot(); 26 | 27 | // Write to a file 28 | final File screenshotFile = new File("my_screenshot.png"); 29 | await screenshotFile.writeAsBytes(screenshotPixels); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_drag_something/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_drag_something/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I drag something? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Get dependencies 15 | 16 | ```sh 17 | flutter packages get 18 | ``` 19 | 20 | ## 3) Replace the `lib/main.dart` file 21 | 22 | Replace the `lib/main.dart` file with the following: 23 | 24 | ```dart 25 | import 'package:flutter/material.dart'; 26 | 27 | void main() => runApp(MyApp()); 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | home: Scaffold( 34 | appBar: AppBar(), 35 | body: Body(), 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | class Body extends StatefulWidget { 42 | @override 43 | _BodyState createState() => _BodyState(); 44 | } 45 | 46 | class _BodyState extends State { 47 | bool isDragAccepted = false; 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Column( 52 | children: [ 53 | Draggable( 54 | feedback: Container( 55 | height: 100, 56 | width: 100, 57 | decoration: BoxDecoration( 58 | border: Border.all(width: 2.0, color: Colors.black), 59 | ), 60 | ), 61 | child: Container( 62 | height: 100, 63 | width: 100, 64 | decoration: BoxDecoration( 65 | border: Border.all(width: 2.0, color: Colors.black), 66 | ), 67 | child: Center( 68 | child: Text("Draggable"), 69 | ), 70 | ), 71 | childWhenDragging: Container( 72 | height: 100, 73 | width: 100, 74 | decoration: BoxDecoration( 75 | border: Border.all(width: 2.0, color: Colors.grey), 76 | ), 77 | ), 78 | ), 79 | DragTarget( 80 | onAccept: (data) { 81 | setState(() { 82 | isDragAccepted = true; 83 | }); 84 | }, 85 | builder: (BuildContext context, candidateData, rejectedData) { 86 | return Container( 87 | height: 100, 88 | width: 100, 89 | decoration: BoxDecoration( 90 | border: Border.all( 91 | width: 2.0, 92 | color: isDragAccepted ? Colors.green : Colors.black), 93 | ), 94 | child: Center( 95 | child: 96 | Text(isDragAccepted ? "Successful drag!" : "Drag Target"), 97 | ), 98 | ); 99 | }, 100 | ), 101 | ], 102 | ); 103 | } 104 | } 105 | ``` 106 | 107 | ## 4) Replace the `test/widget_test.dart` file. 108 | 109 | Replace the `test/widget_test.dart` file with the following: 110 | 111 | ```dart 112 | import 'package:flutter_test/flutter_test.dart'; 113 | import 'package:ftc/main.dart'; 114 | 115 | void main() { 116 | testWidgets("How do I drag something?", (WidgetTester tester) async { 117 | await tester.pumpWidget(MyApp()); 118 | 119 | Finder draggableFinder = find.text("Draggable"); 120 | Finder dragTargetFinder = find.text("Drag Target"); 121 | 122 | expect(draggableFinder, findsOneWidget); 123 | expect(dragTargetFinder, findsOneWidget); 124 | 125 | await tester.drag(draggableFinder, Offset(0, 100)); 126 | await tester.pump(); 127 | 128 | expect(dragTargetFinder, findsNothing); 129 | 130 | Finder successfulDragFinder = find.text("Successful drag!"); 131 | expect(successfulDragFinder, findsOneWidget); 132 | }); 133 | } 134 | ``` 135 | 136 | 137 | ## 5) Run the flutter tests 138 | 139 | ```sh 140 | flutter test 141 | ``` 142 | 143 | # Run tests from example code in cookbook itself 144 | 145 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 146 | 147 | ```sh 148 | flutter create . 149 | flutter packages get 150 | flutter test 151 | ``` 152 | 153 | # Outputs when recipe was made / updated 154 | 155 | ## Output of running flutter tests 156 | 157 | ```sh 158 | $ flutter test 159 | 00:02 +1: All tests passed! 160 | ``` 161 | 162 | ## Output of running `flutter doctor -v` 163 | 164 | ```sh 165 | $ flutter doctor -v 166 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 167 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 168 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 169 | • Engine revision 2994f7e1e6 170 | • Dart version 2.7.0 171 | 172 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 173 | • Android SDK at /Users/matthew/Library/Android/sdk 174 | • Android NDK location not configured (optional; useful for native profiling support) 175 | • Platform android-29, build-tools 28.0.3 176 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 177 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 178 | • All Android licenses accepted. 179 | 180 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 181 | • Xcode at /Applications/Xcode.app/Contents/Developer 182 | • Xcode 11.3.1, Build version 11C504 183 | • CocoaPods version 1.8.4 184 | 185 | [✓] Android Studio (version 3.4) 186 | • Android Studio at /Applications/Android Studio.app/Contents 187 | • Flutter plugin version 35.2.1 188 | • Dart plugin version 183.6270 189 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 190 | 191 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 192 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 193 | • Flutter plugin version 44.0.3 194 | • Dart plugin version 193.6494.35 195 | 196 | [✓] VS Code (version 1.41.1) 197 | • VS Code at /Applications/Visual Studio Code.app/Contents 198 | • Flutter extension version 3.4.1 199 | 200 | [!] Connected device 201 | • Device 9C251FFBA00174 is not authorized. 202 | You might need to check your device for an authorization dialog. 203 | 204 | ! Doctor found issues in 1 category. 205 | ``` 206 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_drag_something/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | appBar: AppBar(), 11 | body: Body(), 12 | ), 13 | ); 14 | } 15 | } 16 | 17 | class Body extends StatefulWidget { 18 | @override 19 | _BodyState createState() => _BodyState(); 20 | } 21 | 22 | class _BodyState extends State { 23 | bool isDragAccepted = false; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Column( 28 | children: [ 29 | Draggable( 30 | feedback: Container( 31 | height: 100, 32 | width: 100, 33 | decoration: BoxDecoration( 34 | border: Border.all(width: 2.0, color: Colors.black), 35 | ), 36 | ), 37 | child: Container( 38 | height: 100, 39 | width: 100, 40 | decoration: BoxDecoration( 41 | border: Border.all(width: 2.0, color: Colors.black), 42 | ), 43 | child: Center( 44 | child: Text("Draggable"), 45 | ), 46 | ), 47 | childWhenDragging: Container( 48 | height: 100, 49 | width: 100, 50 | decoration: BoxDecoration( 51 | border: Border.all(width: 2.0, color: Colors.grey), 52 | ), 53 | ), 54 | ), 55 | DragTarget( 56 | onAccept: (data) { 57 | setState(() { 58 | isDragAccepted = true; 59 | }); 60 | }, 61 | builder: (BuildContext context, candidateData, rejectedData) { 62 | return Container( 63 | height: 100, 64 | width: 100, 65 | decoration: BoxDecoration( 66 | border: Border.all( 67 | width: 2.0, 68 | color: isDragAccepted ? Colors.green : Colors.black), 69 | ), 70 | child: Center( 71 | child: 72 | Text(isDragAccepted ? "Successful drag!" : "Drag Target"), 73 | ), 74 | ); 75 | }, 76 | ), 77 | ], 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_drag_something/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_drag_something/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:ftc/main.dart'; 3 | 4 | void main() { 5 | testWidgets("How do I drag something?", (WidgetTester tester) async { 6 | await tester.pumpWidget(MyApp()); 7 | 8 | Finder draggableFinder = find.text("Draggable"); 9 | Finder dragTargetFinder = find.text("Drag Target"); 10 | 11 | expect(draggableFinder, findsOneWidget); 12 | expect(dragTargetFinder, findsOneWidget); 13 | 14 | await tester.drag(draggableFinder, Offset(0, 100)); 15 | await tester.pump(); 16 | 17 | expect(dragTargetFinder, findsNothing); 18 | 19 | Finder successfulDragFinder = find.text("Successful drag!"); 20 | expect(successfulDragFinder, findsOneWidget); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_find_something/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_find_something/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I find something ... whether it be a widget, some text, etc? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Get dependencies 15 | 16 | ```sh 17 | flutter packages get 18 | ``` 19 | 20 | ## 3) Replace the `lib/main.dart` file 21 | 22 | Replace the `lib/main.dart` file with the following: 23 | 24 | ```dart 25 | import 'package:flutter/material.dart'; 26 | 27 | void main() => runApp(MyApp()); 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | home: Scaffold( 34 | body: Column( 35 | children: [ 36 | RaisedButton( 37 | onPressed: () => print("rasied button was clicked"), 38 | child: Text("we will find this by searching for a type"), 39 | ), 40 | Text( 41 | "we will find this by searching for text", 42 | ), 43 | Text( 44 | "will will find this by searching for a key", 45 | key: Key("mykey"), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | ``` 54 | 55 | ## 4) Replace the `test/widget_test.dart` file. 56 | 57 | Replace the `test/widget_test.dart` file with the following: 58 | 59 | ```dart 60 | import 'package:flutter/material.dart'; 61 | import 'package:flutter_test/flutter_test.dart'; 62 | 63 | import 'package:ftc/main.dart'; 64 | 65 | void main() { 66 | testWidgets('how do i find something?', (WidgetTester tester) async { 67 | await tester.pumpWidget(MyApp()); 68 | 69 | // Let's find a widget by its type 70 | Finder raisedButtonFinder = find.byType(RaisedButton); 71 | expect(raisedButtonFinder, findsOneWidget); 72 | 73 | // Let's find a specific string of text 74 | Finder textFinder = find.text("we will find this by searching for text"); 75 | expect(textFinder, findsOneWidget); 76 | 77 | // Let's find a widget by its key 78 | Finder keyFinder = find.byKey(Key("mykey")); 79 | expect(keyFinder, findsOneWidget); 80 | }); 81 | } 82 | ``` 83 | 84 | 85 | ## 5) Run the flutter tests 86 | 87 | ```sh 88 | flutter test 89 | ``` 90 | 91 | # Run tests from example code in cookbook itself 92 | 93 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 94 | 95 | ```sh 96 | flutter create . 97 | flutter packages get 98 | flutter test 99 | ``` 100 | 101 | # Outputs when recipe was made / updated 102 | 103 | ## Output of running flutter tests 104 | 105 | ```sh 106 | $ flutter test 107 | 00:01 +1: All tests passed! 108 | ``` 109 | 110 | ## Output of running `flutter doctor -v` 111 | 112 | ```sh 113 | $ flutter doctor -v 114 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 115 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 116 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 117 | • Engine revision 2994f7e1e6 118 | • Dart version 2.7.0 119 | 120 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 121 | • Android SDK at /Users/matthew/Library/Android/sdk 122 | • Android NDK location not configured (optional; useful for native profiling support) 123 | • Platform android-29, build-tools 28.0.3 124 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 125 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 126 | • All Android licenses accepted. 127 | 128 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 129 | • Xcode at /Applications/Xcode.app/Contents/Developer 130 | • Xcode 11.3.1, Build version 11C504 131 | • CocoaPods version 1.8.4 132 | 133 | [✓] Android Studio (version 3.4) 134 | • Android Studio at /Applications/Android Studio.app/Contents 135 | • Flutter plugin version 35.2.1 136 | • Dart plugin version 183.6270 137 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 138 | 139 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 140 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 141 | • Flutter plugin version 44.0.3 142 | • Dart plugin version 193.6494.35 143 | 144 | [✓] VS Code (version 1.41.1) 145 | • VS Code at /Applications/Visual Studio Code.app/Contents 146 | • Flutter extension version 3.4.1 147 | 148 | [!] Connected device 149 | ! No devices available 150 | 151 | ! Doctor found issues in 1 category. 152 | ``` 153 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_find_something/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Column( 11 | children: [ 12 | RaisedButton( 13 | onPressed: () => print("rasied button was clicked"), 14 | child: Text("we will find this by searching for a type"), 15 | ), 16 | Text( 17 | "we will find this by searching for text", 18 | ), 19 | Text( 20 | "will will find this by searching for a key", 21 | key: Key("mykey"), 22 | ), 23 | ], 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_find_something/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_find_something/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:ftc/main.dart'; 5 | 6 | void main() { 7 | testWidgets('how do i find something?', (WidgetTester tester) async { 8 | await tester.pumpWidget(MyApp()); 9 | 10 | // Let's find a widget by its type 11 | Finder raisedButtonFinder = find.byType(RaisedButton); 12 | expect(raisedButtonFinder, findsOneWidget); 13 | 14 | // Let's find a specific string of text 15 | Finder textFinder = find.text("we will find this by searching for text"); 16 | expect(textFinder, findsOneWidget); 17 | 18 | // Let's find a widget by its key 19 | Finder keyFinder = find.byKey(Key("mykey")); 20 | expect(keyFinder, findsOneWidget); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_async_http_request/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | 45 | 46 | # Flutter Test Cookbook (flutter_test) 47 | .metadata 48 | android/ 49 | ios/ 50 | pubspec.lock 51 | ftc.iml 52 | *.*~ 53 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_async_http_request/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I mock an async http request? 4 | 5 | # Answer 6 | 7 | We will be using the [http](https://pub.dev/packages/http) package to make an http request, and [mockito](https://pub.dev/packages/mockito) to mock it. 8 | 9 | 10 | ## 1) Create a flutter application. 11 | 12 | ```sh 13 | flutter create ftc 14 | cd ftc 15 | ``` 16 | 17 | ## 2) Add http and mockito to your `pubspec.yaml` file 18 | 19 | ```yaml 20 | dependencies: 21 | flutter: 22 | sdk: flutter 23 | http: 0.12.1 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | mockito: 4.1.1 29 | ``` 30 | 31 | Then get the dependencies by running: 32 | 33 | ```sh 34 | flutter packages get 35 | ``` 36 | 37 | ## 3) Replace the `lib/main.dart` file 38 | 39 | Replace the `lib/main.dart` file with the following: 40 | 41 | ```dart 42 | import 'dart:convert'; 43 | 44 | import 'package:flutter/material.dart'; 45 | import 'package:http/http.dart' as http; 46 | 47 | void main() => runApp(MyApp()); 48 | 49 | class MyApp extends StatelessWidget { 50 | MyApp(); 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return MaterialApp( 55 | home: Scaffold( 56 | body: Body(), 57 | ), 58 | ); 59 | } 60 | } 61 | 62 | class Body extends StatefulWidget { 63 | @override 64 | BodyState createState() => BodyState(); 65 | } 66 | 67 | class BodyState extends State { 68 | Todo todo; 69 | 70 | Future asyncOnPressed() async { 71 | http.Response response = 72 | await http.get('https://jsonplaceholder.typicode.com/todos/1'); 73 | setState(() { 74 | todo = Todo.fromJson(jsonDecode(response.body)); 75 | }); 76 | } 77 | 78 | @override 79 | Widget build(BuildContext context) { 80 | return ListView( 81 | children: [ 82 | RaisedButton( 83 | child: Text('Fetch todo'), 84 | onPressed: asyncOnPressed, 85 | ), 86 | SizedBox( 87 | height: 16, 88 | ), 89 | todo == null 90 | ? Container() 91 | : Text('Todo: ${todo.title}', key: Key("todo-${todo.id}")), 92 | ], 93 | ); 94 | } 95 | } 96 | 97 | class Todo { 98 | final String title; 99 | final int id; 100 | 101 | Todo({ 102 | @required this.title, 103 | @required this.id, 104 | }); 105 | 106 | Todo.fromJson(Map json) 107 | : title = json['title'], 108 | id = json['id']; 109 | } 110 | ``` 111 | 112 | ## 4) Replace the `test/widget_test.dart` file. 113 | 114 | Replace the `test/widget_test.dart` file with the following: 115 | 116 | ```dart 117 | import 'dart:convert'; 118 | import 'package:flutter/material.dart'; 119 | import 'package:flutter_test/flutter_test.dart'; 120 | import 'package:http/http.dart' as http; 121 | import 'package:ftc/main.dart'; 122 | import 'package:mockito/mockito.dart'; 123 | 124 | class MockAppHttpClient extends Mock implements http.Client {} 125 | 126 | const String applicationJson = 'application/json; charset=utf-8'; 127 | 128 | void main() { 129 | testWidgets("A todo item should appear on the screen", 130 | (WidgetTester tester) async { 131 | await tester.pumpWidget(MyApp()); 132 | 133 | // A todo should not be shown when the page loads 134 | expect(find.byKey(Key('todo-1')), findsNothing); 135 | 136 | // Use mockito to mock an http request's response 137 | httpClient = MockAppHttpClient(); 138 | when( 139 | httpClient.get( 140 | 'https://jsonplaceholder.typicode.com/todos/1', 141 | ), 142 | ).thenAnswer((_) async { 143 | return http.Response( 144 | jsonEncode({'id': 1, 'title': 'Do the laundry'}), 200, 145 | headers: {'content-type': 'application/json; charset=utf-8'}); 146 | }); 147 | 148 | // Make the http request 149 | // 150 | // IMPORTANT NOTE: We are *not* tapping the [RaisedButton] directly. 151 | // Instead, we are finding the state object, and then running the asyncOnPressed function 152 | // which is passed to the onPressed of the RaisedButton. If we were to tap the RaisedButton directly, 153 | // the test wouldn't wait properly because the onPressed signature is a VoidCallback instead 154 | // of an AsyncCallback. 155 | await tester.state(find.byType(Body)).asyncOnPressed(); 156 | await tester.pump(); 157 | 158 | // A todo should be shown after the http request receives a response 159 | expect(find.byKey(Key('todo-1')), findsOneWidget); 160 | }); 161 | } 162 | ``` 163 | 164 | 165 | ## 5) Run the flutter tests 166 | 167 | ```sh 168 | flutter test 169 | ``` 170 | 171 | # Run tests from example code in cookbook itself 172 | 173 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 174 | 175 | ```sh 176 | flutter create . 177 | flutter packages get 178 | flutter test 179 | ``` 180 | 181 | # Outputs when recipe was made / updated 182 | 183 | ## Output of running flutter tests 184 | 185 | ```sh 186 | $ flutter test 187 | 00:02 +1: All tests passed! 188 | ``` 189 | 190 | ## Output of running `flutter doctor -v` 191 | 192 | ```sh 193 | $ flutter doctor -v 194 | [✓] Flutter (Channel stable, v1.17.2, on Mac OS X 10.15.5 19F101, locale en-US) 195 | • Flutter version 1.17.2 at /Users/matthew/gadfly/flutter_versions/flutter_1.17.2 196 | • Framework revision 5f21edf8b6 (2 weeks ago), 2020-05-28 12:44:12 -0700 197 | • Engine revision b851c71829 198 | • Dart version 2.8.3 199 | 200 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 201 | • Android SDK at /Users/matthew/Library/Android/sdk 202 | • Platform android-29, build-tools 28.0.3 203 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 204 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 205 | • All Android licenses accepted. 206 | 207 | [✓] Xcode - develop for iOS and macOS (Xcode 11.5) 208 | • Xcode at /Applications/Xcode.app/Contents/Developer 209 | • Xcode 11.5, Build version 11E608c 210 | • CocoaPods version 1.9.1 211 | 212 | [✓] Android Studio (version 3.4) 213 | • Android Studio at /Applications/Android Studio.app/Contents 214 | • Flutter plugin version 35.2.1 215 | • Dart plugin version 183.6270 216 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 217 | 218 | [!] IntelliJ IDEA Ultimate Edition (version 2020.1.2) 219 | • IntelliJ at /Applications/IntelliJ IDEA.app 220 | ✗ Flutter plugin not installed; this adds Flutter specific functionality. 221 | ✗ Dart plugin not installed; this adds Dart specific functionality. 222 | • For information about installing plugins, see 223 | https://flutter.dev/intellij-setup/#installing-the-plugins 224 | 225 | [✓] VS Code (version 1.45.1) 226 | • VS Code at /Applications/Visual Studio Code.app/Contents 227 | • Flutter extension version 3.10.2 228 | 229 | [✓] Connected device (1 available) 230 | • iPhone 8 • C9868AFF-E4CE-4C0B-AC2B-9600508F6C1F • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-5 (simulator) 231 | 232 | ! Doctor found issues in 1 category. 233 | ``` 234 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_async_http_request/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:http/http.dart' as http; 4 | 5 | void main() => runApp(MyApp()); 6 | 7 | http.Client httpClient = http.Client(); 8 | 9 | class MyApp extends StatelessWidget { 10 | MyApp(); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | home: Scaffold( 16 | body: Body(), 17 | ), 18 | ); 19 | } 20 | } 21 | 22 | class Body extends StatefulWidget { 23 | @override 24 | BodyState createState() => BodyState(); 25 | } 26 | 27 | class BodyState extends State { 28 | Todo todo; 29 | 30 | Future asyncOnPressed() async { 31 | http.Response response = 32 | await httpClient.get('https://jsonplaceholder.typicode.com/todos/1'); 33 | setState(() { 34 | todo = Todo.fromJson(jsonDecode(response.body)); 35 | }); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return ListView( 41 | children: [ 42 | RaisedButton( 43 | child: Text('Fetch todo'), 44 | onPressed: asyncOnPressed, 45 | ), 46 | SizedBox( 47 | height: 16, 48 | ), 49 | todo == null 50 | ? Container() 51 | : Text('Todo: ${todo.title}', key: Key("todo-${todo.id}")), 52 | ], 53 | ); 54 | } 55 | } 56 | 57 | class Todo { 58 | final String title; 59 | final int id; 60 | 61 | Todo({ 62 | @required this.title, 63 | @required this.id, 64 | }); 65 | 66 | Todo.fromJson(Map json) 67 | : title = json['title'], 68 | id = json['id']; 69 | } 70 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_async_http_request/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | http: 0.12.1 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | mockito: 4.1.1 -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_async_http_request/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:http/http.dart' as http; 5 | import 'package:ftc/main.dart'; 6 | import 'package:mockito/mockito.dart'; 7 | 8 | class MockAppHttpClient extends Mock implements http.Client {} 9 | 10 | const String applicationJson = 'application/json; charset=utf-8'; 11 | 12 | void main() { 13 | testWidgets("A todo item should appear on the screen", 14 | (WidgetTester tester) async { 15 | await tester.pumpWidget(MyApp()); 16 | 17 | // A todo should not be shown when the page loads 18 | expect(find.byKey(Key('todo-1')), findsNothing); 19 | 20 | // Use mockito to mock an http request's response 21 | httpClient = MockAppHttpClient(); 22 | when( 23 | httpClient.get( 24 | 'https://jsonplaceholder.typicode.com/todos/1', 25 | ), 26 | ).thenAnswer((_) async { 27 | return http.Response( 28 | jsonEncode({'id': 1, 'title': 'Do the laundry'}), 200, 29 | headers: {'content-type': 'application/json; charset=utf-8'}); 30 | }); 31 | 32 | // Make the http request 33 | // 34 | // IMPORTANT NOTE: We are *not* tapping the [RaisedButton] directly. 35 | // Instead, we are finding the state object, and then running the asyncOnPressed function 36 | // which is passed to the onPressed of the RaisedButton. If we were to tap the RaisedButton directly, 37 | // the test wouldn't wait properly because the onPressed signature is a VoidCallback instead 38 | // of an AsyncCallback. 39 | await tester.state(find.byType(Body)).asyncOnPressed(); 40 | await tester.pump(); 41 | 42 | // A todo should be shown after the http request receives a response 43 | expect(find.byKey(Key('todo-1')), findsOneWidget); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_shared_preferences/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_shared_preferences/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I mock [shared_preferences](https://pub.dev/packages/shared_preferences)? 4 | 5 | # Answer 6 | 7 | There are a few subtle things that can go wrong when testing shared_preferences. 8 | 9 | a) If you are grabbing a value from shared_preferences during `initState`, then there is the need for an extra `pump` when testing. 10 | b) If you try to get shared_preferences in your main file before your call to `runApp`, you may experience an error. If so, you will need to add a call to [WidgetsFlutterBinding.ensureInitialized](https://api.flutter.dev/flutter/widgets/WidgetsFlutterBinding/ensureInitialized.html) (as well as [TestWidgetsFlutterBinding.ensureInitialized](https://api.flutter.dev/flutter/flutter_test/TestWidgetsFlutterBinding/ensureInitialized.html) to your tests). 11 | 12 | This recipe covers a). 13 | 14 | ## 1) Create a flutter application. 15 | 16 | ```sh 17 | flutter create ftc 18 | cd ftc 19 | ``` 20 | 21 | ## 2) Add shared_preferences to your `pubspec.yaml` file 22 | 23 | ```yaml 24 | dependencies: 25 | flutter: 26 | sdk: flutter 27 | shared_preferences: 0.5.6+2 28 | ``` 29 | 30 | Then get the dependencies by running: 31 | 32 | ```sh 33 | flutter packages get 34 | ``` 35 | 36 | ## 3) Replace the `lib/main.dart` file 37 | 38 | Replace the `lib/main.dart` file with the following: 39 | 40 | ```dart 41 | import 'package:flutter/material.dart'; 42 | import 'package:shared_preferences/shared_preferences.dart'; 43 | 44 | void main() => runApp(MyApp()); 45 | 46 | class MyApp extends StatelessWidget { 47 | MyApp(); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return MaterialApp( 52 | home: Scaffold( 53 | body: Body(), 54 | ), 55 | ); 56 | } 57 | } 58 | 59 | class Body extends StatefulWidget { 60 | @override 61 | _BodyState createState() => _BodyState(); 62 | } 63 | 64 | class _BodyState extends State { 65 | int spValue = 0; 66 | 67 | @override 68 | void initState() { 69 | super.initState(); 70 | SharedPreferences.getInstance().then((SharedPreferences prefs) { 71 | setState(() { 72 | spValue = prefs.getInt("myValue") ?? 0; 73 | }); 74 | }); 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return Center( 80 | child: Text("The shared_preferences value is: $spValue"), 81 | ); 82 | } 83 | } 84 | ``` 85 | 86 | ## 4) Replace the `test/widget_test.dart` file. 87 | 88 | Replace the `test/widget_test.dart` file with the following: 89 | 90 | ```dart 91 | import 'package:flutter_test/flutter_test.dart'; 92 | import 'package:shared_preferences/shared_preferences.dart'; 93 | 94 | import 'package:ftc/main.dart'; 95 | 96 | void main() { 97 | // If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding. 98 | TestWidgetsFlutterBinding.ensureInitialized(); 99 | 100 | testWidgets( 101 | "The value displayed should fallback to 0 if the shared_preferences value isn't mocked", 102 | (WidgetTester tester) async { 103 | await tester.pumpWidget(MyApp()); 104 | 105 | expect(find.text("The shared_preferences value is: 0"), findsOneWidget); 106 | }); 107 | 108 | testWidgets( 109 | "The value displayed should be updated when mocking initial shared_preferences values", 110 | (WidgetTester tester) async { 111 | // We are setting the initial value to 10 112 | SharedPreferences.setMockInitialValues({"myValue": 10}); 113 | 114 | // then we are pumping our top-level widget 115 | await tester.pumpWidget(MyApp()); 116 | 117 | // However, because we are grabbing shared_preferences value in our initState, and then using setState 118 | // to set the value displayed in our app, we need to pump one more time! 119 | await tester.pump(); 120 | 121 | // Then we expect out mock value to be displayed 122 | expect(find.text("The shared_preferences value is: 10"), findsOneWidget); 123 | }); 124 | } 125 | ``` 126 | 127 | 128 | ## 5) Run the flutter tests 129 | 130 | ```sh 131 | flutter test 132 | ``` 133 | 134 | # Run tests from example code in cookbook itself 135 | 136 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 137 | 138 | ```sh 139 | flutter create . 140 | flutter packages get 141 | flutter test 142 | ``` 143 | 144 | # Outputs when recipe was made / updated 145 | 146 | ## Output of running flutter tests 147 | 148 | ```sh 149 | $ flutter test 150 | 00:06 +1: All tests passed! 151 | ``` 152 | 153 | ## Output of running `flutter doctor -v` 154 | 155 | ```sh 156 | $ flutter doctor -v 157 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 158 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 159 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 160 | • Engine revision 2994f7e1e6 161 | • Dart version 2.7.0 162 | 163 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 164 | • Android SDK at /Users/matthew/Library/Android/sdk 165 | • Android NDK location not configured (optional; useful for native profiling support) 166 | • Platform android-29, build-tools 28.0.3 167 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 168 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 169 | • All Android licenses accepted. 170 | 171 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 172 | • Xcode at /Applications/Xcode.app/Contents/Developer 173 | • Xcode 11.3.1, Build version 11C504 174 | • CocoaPods version 1.8.4 175 | 176 | [✓] Android Studio (version 3.4) 177 | • Android Studio at /Applications/Android Studio.app/Contents 178 | • Flutter plugin version 35.2.1 179 | • Dart plugin version 183.6270 180 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 181 | 182 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 183 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 184 | • Flutter plugin version 44.0.3 185 | • Dart plugin version 193.6494.35 186 | 187 | [✓] VS Code (version 1.41.1) 188 | • VS Code at /Applications/Visual Studio Code.app/Contents 189 | • Flutter extension version 3.4.1 190 | 191 | [✓] Connected device (1 available) 192 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 193 | 194 | • No issues found! 195 | ``` 196 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_shared_preferences/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | void main() => runApp(MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | MyApp(); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | home: Scaffold( 13 | body: Body(), 14 | ), 15 | ); 16 | } 17 | } 18 | 19 | class Body extends StatefulWidget { 20 | @override 21 | _BodyState createState() => _BodyState(); 22 | } 23 | 24 | class _BodyState extends State { 25 | int spValue = 0; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | SharedPreferences.getInstance().then((SharedPreferences prefs) { 31 | setState(() { 32 | spValue = prefs.getInt("myValue") ?? 0; 33 | }); 34 | }); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Center( 40 | child: Text("The shared_preferences value is: $spValue"), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_shared_preferences/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | shared_preferences: 0.5.6+2 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_mock_shared_preferences/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | import 'package:ftc/main.dart'; 5 | 6 | void main() { 7 | // If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding. 8 | TestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | testWidgets( 11 | "The value displayed should fallback to 0 if the shared_preferences value isn't mocked", 12 | (WidgetTester tester) async { 13 | await tester.pumpWidget(MyApp()); 14 | 15 | expect(find.text("The shared_preferences value is: 0"), findsOneWidget); 16 | }); 17 | 18 | testWidgets( 19 | "The value displayed should be updated when mocking initial shared_preferences values", 20 | (WidgetTester tester) async { 21 | // We are setting the initial value to 10 22 | SharedPreferences.setMockInitialValues({"myValue": 10}); 23 | 24 | // then we are pumping our top-level widget 25 | await tester.pumpWidget(MyApp()); 26 | 27 | // However, because we are grabbing shared_preferences value in our initState, and then using setState 28 | // to set the value displayed in our app, we need to pump one more time! 29 | await tester.pump(); 30 | 31 | // Then we expect out mock value to be displayed 32 | expect(find.text("The shared_preferences value is: 10"), findsOneWidget); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_open_a_drawer/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_open_a_drawer/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I open a [Drawer](https://api.flutter.dev/flutter/material/Drawer-class.html)? 4 | 5 | # Answer 6 | 7 | There are several methods to open a Drawer: 8 | 9 | a) Open Drawer by tapping the menu icon in the Appbar 10 | b) Open Drawer by using scaffoldKey 11 | c) Open Drawer by using a BuildContext and finding a Scaffold with Scaffold.of 12 | d) Open Drawer by swiping from the left edge 13 | 14 | ## 1) Create a flutter application. 15 | 16 | ```sh 17 | flutter create ftc 18 | cd ftc 19 | ``` 20 | 21 | ## 2) Get dependencies 22 | 23 | ```sh 24 | flutter packages get 25 | ``` 26 | 27 | ## 3) Replace the `lib/main.dart` file 28 | 29 | Replace the `lib/main.dart` file with the following: 30 | 31 | ```dart 32 | import 'package:flutter/material.dart'; 33 | 34 | void main() => runApp(MyApp()); 35 | 36 | GlobalKey scaffoldKey = GlobalKey(); 37 | 38 | class MyApp extends StatelessWidget { 39 | @override 40 | Widget build(BuildContext context) { 41 | return MaterialApp( 42 | home: Scaffold( 43 | key: scaffoldKey, 44 | appBar: AppBar(), 45 | drawer: Drawer(), 46 | body: Body(), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | class Body extends StatelessWidget { 53 | @override 54 | Widget build(BuildContext context) { 55 | return Center( 56 | child: Text("There is a drawer"), 57 | ); 58 | } 59 | } 60 | ``` 61 | 62 | ## 4) Replace the `test/widget_test.dart` file. 63 | 64 | Replace the `test/widget_test.dart` file with the following: 65 | 66 | ```dart 67 | import 'package:flutter/material.dart'; 68 | import 'package:flutter_test/flutter_test.dart'; 69 | 70 | import 'package:ftc/main.dart'; 71 | 72 | void main() { 73 | testWidgets('How do I open a Drawer?', (WidgetTester tester) async { 74 | await tester.pumpWidget(MyApp()); 75 | 76 | Finder drawerFinder = find.byType(Drawer); 77 | 78 | expect(drawerFinder, findsNothing); 79 | 80 | // 81 | // Method a) 82 | // 83 | 84 | // open Drawer by tapping the menu icon in the Appbar 85 | Finder menuIconFinder = find.byIcon(Icons.menu); 86 | await tester.tap(menuIconFinder); 87 | await tester.pumpAndSettle(); 88 | 89 | // confirm that the Drawer is open 90 | expect(drawerFinder, findsOneWidget); 91 | 92 | // close Drawer 93 | Navigator.of(tester.element(drawerFinder)).pop(); 94 | await tester.pumpAndSettle(); 95 | expect(drawerFinder, findsNothing); 96 | 97 | // 98 | // Method b) 99 | // 100 | 101 | // open Drawer by using scaffoldKey 102 | scaffoldKey.currentState.openDrawer(); 103 | await tester.pumpAndSettle(); 104 | 105 | // confirm that the Drawer is open 106 | expect(drawerFinder, findsOneWidget); 107 | 108 | // close Drawer 109 | Navigator.of(tester.element(drawerFinder)).pop(); 110 | await tester.pumpAndSettle(); 111 | expect(drawerFinder, findsNothing); 112 | 113 | // 114 | // Method c) 115 | // 116 | 117 | // open Drawer by using a BuildContext and finding a Scaffold with Scaffold.of 118 | Finder bodyFinder = find.byType(Body); 119 | Scaffold.of(tester.element(bodyFinder)).openDrawer(); 120 | await tester.pumpAndSettle(); 121 | 122 | // confirm that the Drawer is open 123 | expect(drawerFinder, findsOneWidget); 124 | 125 | // close Drawer 126 | Navigator.of(tester.element(drawerFinder)).pop(); 127 | await tester.pumpAndSettle(); 128 | expect(drawerFinder, findsNothing); 129 | 130 | // 131 | // Method d) 132 | // 133 | 134 | // open Drawer by swiping from the left edge 135 | await tester.dragFrom(Offset(0, 100), Offset(300, 100)); 136 | await tester.pumpAndSettle(); 137 | 138 | // confirm that the Drawer is open 139 | expect(drawerFinder, findsOneWidget); 140 | 141 | // close Drawer 142 | Navigator.of(tester.element(drawerFinder)).pop(); 143 | await tester.pumpAndSettle(); 144 | expect(drawerFinder, findsNothing); 145 | }); 146 | } 147 | ``` 148 | 149 | 150 | ## 5) Run the flutter tests 151 | 152 | ```sh 153 | flutter test 154 | ``` 155 | 156 | # Run tests from example code in cookbook itself 157 | 158 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 159 | 160 | ```sh 161 | flutter create . 162 | flutter packages get 163 | flutter test 164 | ``` 165 | 166 | # Outputs when recipe was made / updated 167 | 168 | ## Output of running flutter tests 169 | 170 | ```sh 171 | $ flutter test 172 | 00:02 +1: All tests passed! 173 | ``` 174 | 175 | ## Output of running `flutter doctor -v` 176 | 177 | ```sh 178 | $ flutter doctor -v 179 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 180 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 181 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 182 | • Engine revision 2994f7e1e6 183 | • Dart version 2.7.0 184 | 185 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 186 | • Android SDK at /Users/matthew/Library/Android/sdk 187 | • Android NDK location not configured (optional; useful for native profiling support) 188 | • Platform android-29, build-tools 28.0.3 189 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 190 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 191 | • All Android licenses accepted. 192 | 193 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 194 | • Xcode at /Applications/Xcode.app/Contents/Developer 195 | • Xcode 11.3.1, Build version 11C504 196 | • CocoaPods version 1.8.4 197 | 198 | [✓] Android Studio (version 3.4) 199 | • Android Studio at /Applications/Android Studio.app/Contents 200 | • Flutter plugin version 35.2.1 201 | • Dart plugin version 183.6270 202 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 203 | 204 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 205 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 206 | • Flutter plugin version 44.0.3 207 | • Dart plugin version 193.6494.35 208 | 209 | [✓] VS Code (version 1.41.1) 210 | • VS Code at /Applications/Visual Studio Code.app/Contents 211 | • Flutter extension version 3.4.1 212 | 213 | [✓] Connected device (1 available) 214 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 215 | 216 | • No issues found! 217 | ``` -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_open_a_drawer/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | GlobalKey scaffoldKey = GlobalKey(); 6 | 7 | class MyApp extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | home: Scaffold( 12 | key: scaffoldKey, 13 | appBar: AppBar(), 14 | drawer: Drawer(), 15 | body: Body(), 16 | ), 17 | ); 18 | } 19 | } 20 | 21 | class Body extends StatelessWidget { 22 | @override 23 | Widget build(BuildContext context) { 24 | return Center( 25 | child: Text("There is a drawer"), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_open_a_drawer/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | # needed to show menu icon in AppBar 18 | flutter: 19 | uses-material-design: true -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_open_a_drawer/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:ftc/main.dart'; 5 | 6 | void main() { 7 | testWidgets('How do I open a Drawer?', (WidgetTester tester) async { 8 | await tester.pumpWidget(MyApp()); 9 | 10 | Finder drawerFinder = find.byType(Drawer); 11 | 12 | expect(drawerFinder, findsNothing); 13 | 14 | // 15 | // Method a) 16 | // 17 | 18 | // open Drawer by tapping the menu icon in the Appbar 19 | Finder menuIconFinder = find.byIcon(Icons.menu); 20 | await tester.tap(menuIconFinder); 21 | await tester.pumpAndSettle(); 22 | 23 | // confirm that the Drawer is open 24 | expect(drawerFinder, findsOneWidget); 25 | 26 | // close Drawer 27 | Navigator.of(tester.element(drawerFinder)).pop(); 28 | await tester.pumpAndSettle(); 29 | expect(drawerFinder, findsNothing); 30 | 31 | // 32 | // Method b) 33 | // 34 | 35 | // open Drawer by using scaffoldKey 36 | scaffoldKey.currentState.openDrawer(); 37 | await tester.pumpAndSettle(); 38 | 39 | // confirm that the Drawer is open 40 | expect(drawerFinder, findsOneWidget); 41 | 42 | // close Drawer 43 | Navigator.of(tester.element(drawerFinder)).pop(); 44 | await tester.pumpAndSettle(); 45 | expect(drawerFinder, findsNothing); 46 | 47 | // 48 | // Method c) 49 | // 50 | 51 | // open Drawer by using a BuildContext and finding a Scaffold with Scaffold.of 52 | Finder bodyFinder = find.byType(Body); 53 | Scaffold.of(tester.element(bodyFinder)).openDrawer(); 54 | await tester.pumpAndSettle(); 55 | 56 | // confirm that the Drawer is open 57 | expect(drawerFinder, findsOneWidget); 58 | 59 | // close Drawer 60 | Navigator.of(tester.element(drawerFinder)).pop(); 61 | await tester.pumpAndSettle(); 62 | expect(drawerFinder, findsNothing); 63 | 64 | // 65 | // Method d) 66 | // 67 | 68 | // open Drawer by swiping from the left edge 69 | await tester.dragFrom(Offset(0, 100), Offset(300, 100)); 70 | await tester.pumpAndSettle(); 71 | 72 | // confirm that the Drawer is open 73 | expect(drawerFinder, findsOneWidget); 74 | 75 | // close Drawer 76 | Navigator.of(tester.element(drawerFinder)).pop(); 77 | await tester.pumpAndSettle(); 78 | expect(drawerFinder, findsNothing); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_flutter_test/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_flutter_test/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I run a [flutter test](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html) test? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Get dependencies 15 | 16 | ```sh 17 | flutter packages get 18 | ``` 19 | 20 | ## 3) Replace the `lib/main.dart` file 21 | 22 | Replace the `lib/main.dart` file with the following: 23 | 24 | ```dart 25 | import 'package:flutter/material.dart'; 26 | 27 | void main() => runApp(MyApp()); 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | home: Scaffold( 34 | body: Center( 35 | child: Text("Hello world"), 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | ``` 42 | 43 | ## 4) Replace the `test/widget_test.dart` file. 44 | 45 | Replace the `test/widget_test.dart` file with the following: 46 | 47 | ```dart 48 | import 'package:flutter_test/flutter_test.dart'; 49 | import 'package:ftc/main.dart'; 50 | 51 | void main() { 52 | testWidgets("'Hello world' text exists", (WidgetTester tester) async { 53 | await tester.pumpWidget(MyApp()); 54 | 55 | expect(find.text('Hello world'), findsOneWidget); 56 | }); 57 | } 58 | ``` 59 | 60 | 61 | ## 5) Run the flutter tests 62 | 63 | ```sh 64 | flutter test 65 | ``` 66 | 67 | # Run tests from example code in cookbook itself 68 | 69 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 70 | 71 | ```sh 72 | flutter create . 73 | flutter packages get 74 | flutter test 75 | ``` 76 | 77 | # Outputs when recipe was made / updated 78 | 79 | ## Output of running flutter tests 80 | 81 | ```sh 82 | $ flutter test 83 | 00:06 +1: All tests passed! 84 | ``` 85 | 86 | ## Output of running `flutter doctor -v` 87 | 88 | ```sh 89 | $ flutter doctor -v 90 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 91 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 92 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 93 | • Engine revision 2994f7e1e6 94 | • Dart version 2.7.0 95 | 96 | 97 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 98 | • Android SDK at /Users/matthew/Library/Android/sdk 99 | • Android NDK location not configured (optional; useful for native profiling support) 100 | • Platform android-29, build-tools 28.0.3 101 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 102 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 103 | • All Android licenses accepted. 104 | 105 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 106 | • Xcode at /Applications/Xcode.app/Contents/Developer 107 | • Xcode 11.3.1, Build version 11C504 108 | • CocoaPods version 1.8.4 109 | 110 | [✓] Android Studio (version 3.4) 111 | • Android Studio at /Applications/Android Studio.app/Contents 112 | • Flutter plugin version 35.2.1 113 | • Dart plugin version 183.6270 114 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 115 | 116 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 117 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 118 | • Flutter plugin version 44.0.3 119 | • Dart plugin version 193.6494.35 120 | 121 | [✓] VS Code (version 1.41.1) 122 | • VS Code at /Applications/Visual Studio Code.app/Contents 123 | • Flutter extension version 3.4.1 124 | 125 | [!] Connected device 126 | ! No devices available 127 | 128 | ! Doctor found issues in 1 category. 129 | ``` 130 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_flutter_test/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Center( 11 | child: Text("Hello world"), 12 | ), 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_flutter_test/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_flutter_test/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:ftc/main.dart'; 3 | 4 | void main() { 5 | testWidgets("'Hello world' text exists", (WidgetTester tester) async { 6 | await tester.pumpWidget(MyApp()); 7 | 8 | expect(find.text('Hello world'), findsOneWidget); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_script/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_script/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I run a script inside of a test? 4 | 5 | # Answer 6 | 7 | Use [Process.run](https://api.dart.dev/stable/2.7.1/dart-io/Process/run.html) 8 | 9 | ## 1) Create a flutter application. 10 | 11 | ```sh 12 | flutter create ftc 13 | cd ftc 14 | ``` 15 | 16 | ## 2) Get dependencies 17 | 18 | ```sh 19 | flutter packages get 20 | ``` 21 | 22 | ## 3) Replace the `lib/main.dart` file 23 | 24 | Replace the `lib/main.dart` file with the following: 25 | 26 | ```dart 27 | import 'package:flutter/material.dart'; 28 | 29 | void main() => runApp(MyApp()); 30 | 31 | class MyApp extends StatelessWidget { 32 | @override 33 | Widget build(BuildContext context) { 34 | return MaterialApp( 35 | home: Scaffold( 36 | body: Center( 37 | child: Text("A script will be run during the test"), 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | ``` 44 | 45 | ## 4) Replace the `test/widget_test.dart` file. 46 | 47 | Replace the `test/widget_test.dart` file with the following: 48 | 49 | ```dart 50 | import 'package:flutter_test/flutter_test.dart'; 51 | import 'package:ftc/main.dart'; 52 | import 'dart:io'; 53 | 54 | void main() async { 55 | testWidgets("How do I run a script?", (WidgetTester tester) async { 56 | await tester.pumpWidget(MyApp()); 57 | 58 | // Running a script inside of flutter_test is different than with flutter_driver. 59 | // With flutter_test, we need to: 60 | // a) use tester.runAsync, and 61 | // b) write the path of the script from the perspective of this file 62 | ProcessResult result = await tester.runAsync(() async { 63 | return await Process.run('../echo.sh', ['1']); 64 | }); 65 | 66 | //Note: echo appends a newline to the result 67 | expect(result.stdout, '1\n'); 68 | }); 69 | } 70 | ``` 71 | 72 | ## 5) Add an `echo.sh` script 73 | 74 | Create a file called `echo.sh` and add the following to it: 75 | 76 | ```sh 77 | #!/bin/bash 78 | 79 | echo "$1" 80 | ``` 81 | 82 | Then run: 83 | 84 | ```sh 85 | chmod +x echo.sh 86 | ``` 87 | 88 | 89 | 90 | ## 6) Run the flutter tests 91 | 92 | ```sh 93 | flutter test 94 | ``` 95 | 96 | # Run tests from example code in cookbook itself 97 | 98 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 99 | 100 | ```sh 101 | flutter create . 102 | flutter packages get 103 | flutter test 104 | ``` 105 | 106 | # Outputs when recipe was made / updated 107 | 108 | ## Output of running flutter tests 109 | 110 | ```sh 111 | $ flutter test 112 | 00:01 +1: All tests passed! 113 | ``` 114 | 115 | ## Output of running `flutter doctor -v` 116 | 117 | ```sh 118 | $ flutter doctor -v 119 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 120 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 121 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 122 | • Engine revision 2994f7e1e6 123 | • Dart version 2.7.0 124 | 125 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 126 | • Android SDK at /Users/matthew/Library/Android/sdk 127 | • Android NDK location not configured (optional; useful for native profiling support) 128 | • Platform android-29, build-tools 28.0.3 129 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 130 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 131 | • All Android licenses accepted. 132 | 133 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 134 | • Xcode at /Applications/Xcode.app/Contents/Developer 135 | • Xcode 11.3.1, Build version 11C504 136 | • CocoaPods version 1.8.4 137 | 138 | [✓] Android Studio (version 3.4) 139 | • Android Studio at /Applications/Android Studio.app/Contents 140 | • Flutter plugin version 35.2.1 141 | • Dart plugin version 183.6270 142 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 143 | 144 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 145 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 146 | • Flutter plugin version 44.0.3 147 | • Dart plugin version 193.6494.35 148 | 149 | [✓] VS Code (version 1.41.1) 150 | • VS Code at /Applications/Visual Studio Code.app/Contents 151 | • Flutter extension version 3.4.1 152 | 153 | [✓] Connected device (1 available) 154 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 155 | 156 | • No issues found! 157 | ``` 158 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_script/echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "$1" 4 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_script/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Center( 11 | child: Text("Hello world"), 12 | ), 13 | ), 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_script/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_run_a_script/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:ftc/main.dart'; 3 | import 'dart:io'; 4 | 5 | void main() async { 6 | testWidgets("How do I run a script?", (WidgetTester tester) async { 7 | await tester.pumpWidget(MyApp()); 8 | 9 | // Running a script inside of flutter_test is different than with flutter_driver. 10 | // With flutter_test, we need to: 11 | // a) use tester.runAsync, and 12 | // b) write the path of the script from the perspective of this file 13 | ProcessResult result = await tester.runAsync(() async { 14 | return await Process.run('../echo.sh', ['1']); 15 | }); 16 | 17 | //Note: echo appends a newline to the result 18 | expect(result.stdout, '1\n'); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_send_a_keyboard_action/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_send_a_keyboard_action/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I send a keyboard action like `done` or `next`? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Get dependencies 15 | 16 | ```sh 17 | flutter packages get 18 | ``` 19 | 20 | ## 3) Replace the `lib/main.dart` file 21 | 22 | Replace the `lib/main.dart` file with the following: 23 | 24 | ```dart 25 | import 'package:flutter/material.dart'; 26 | 27 | void main() => runApp(MyApp()); 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | home: Scaffold( 34 | body: MyForm(), 35 | ), 36 | ); 37 | } 38 | } 39 | 40 | class MyForm extends StatefulWidget { 41 | @override 42 | _MyFormState createState() => _MyFormState(); 43 | } 44 | 45 | class _MyFormState extends State { 46 | // We are creating controllers and focus nodes for our two text fields 47 | TextEditingController firstNameController; 48 | FocusNode firstNameFocusNode; 49 | 50 | TextEditingController lastNameController; 51 | FocusNode lastNameFocusNode; 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | firstNameController = TextEditingController(); 57 | firstNameFocusNode = FocusNode(); 58 | 59 | lastNameController = TextEditingController(); 60 | lastNameFocusNode = FocusNode(); 61 | } 62 | 63 | // and we are cleaning up after ourselves by disposing the controllers and focus nodes 64 | @override 65 | void dispose() { 66 | firstNameController.dispose(); 67 | firstNameFocusNode.dispose(); 68 | 69 | lastNameController.dispose(); 70 | lastNameFocusNode.dispose(); 71 | super.dispose(); 72 | } 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return Column( 77 | children: [ 78 | TextField( 79 | decoration: InputDecoration(labelText: "First name"), 80 | textInputAction: TextInputAction.next, 81 | controller: firstNameController, 82 | focusNode: firstNameFocusNode, 83 | // When a user hits 'next' on the keyboard, it will unfocus firstName and focus lastName 84 | onSubmitted: (String _firstName) { 85 | firstNameFocusNode.unfocus(); 86 | FocusScope.of(context).requestFocus(lastNameFocusNode); 87 | }, 88 | ), 89 | TextField( 90 | decoration: InputDecoration(labelText: "Last name"), 91 | textInputAction: TextInputAction.done, 92 | controller: lastNameController, 93 | focusNode: lastNameFocusNode, 94 | // When a user hits 'done' on the keyboard, it will unfocus lastName and then show a SnackBar with the full name 95 | onSubmitted: (String _lastName) { 96 | lastNameFocusNode.unfocus(); 97 | Scaffold.of(context).showSnackBar(SnackBar( 98 | content: Text( 99 | "Your name is: ${firstNameController.text} ${lastNameController.text}"), 100 | )); 101 | }, 102 | ), 103 | ], 104 | ); 105 | } 106 | } 107 | ``` 108 | 109 | ## 4) Replace the `test/widget_test.dart` file. 110 | 111 | Replace the `test/widget_test.dart` file with the following: 112 | 113 | ```dart 114 | import 'package:flutter/material.dart'; 115 | import 'package:flutter_test/flutter_test.dart'; 116 | import 'package:ftc/main.dart'; 117 | 118 | void main() { 119 | testWidgets("How do I send a keyboard action?", (WidgetTester tester) async { 120 | await tester.pumpWidget(MyApp()); 121 | 122 | // First, let's confirm we have two TextFields on the page 123 | Finder firstNameFinder = find.byType(TextField).at(0); 124 | Finder lastNameFinder = find.byType(TextField).at(1); 125 | 126 | expect(firstNameFinder, findsOneWidget); 127 | expect(lastNameFinder, findsOneWidget); 128 | 129 | // Next, let's enter our first name 130 | await tester.showKeyboard(firstNameFinder); 131 | tester.testTextInput.enterText("John"); 132 | 133 | // Tap the 'next' action in the keyboard 134 | await tester.testTextInput.receiveAction(TextInputAction.next); 135 | 136 | // This should automatically focus the last name 137 | // which means we don't need to show a new keyboard with a new finder 138 | tester.testTextInput.enterText("Doe"); 139 | 140 | // Tap the 'done' action in the keyboard 141 | await tester.testTextInput.receiveAction(TextInputAction.done); 142 | 143 | // This should open a SnackBar with the entered name 144 | await tester.pumpAndSettle(); 145 | expect(find.text("Your name is: John Doe"), findsOneWidget); 146 | }); 147 | } 148 | ``` 149 | 150 | 151 | ## 5) Run the flutter tests 152 | 153 | ```sh 154 | flutter test 155 | ``` 156 | 157 | # Run tests from example code in cookbook itself 158 | 159 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 160 | 161 | ```sh 162 | flutter create . 163 | flutter packages get 164 | flutter test 165 | ``` 166 | 167 | # Outputs when recipe was made / updated 168 | 169 | ## Output of running flutter tests 170 | 171 | ```sh 172 | $ flutter test 173 | 00:02 +1: All tests passed! 174 | ``` 175 | 176 | ## Output of running `flutter doctor -v` 177 | 178 | ```sh 179 | $ flutter doctor -v 180 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 181 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 182 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 183 | • Engine revision 2994f7e1e6 184 | • Dart version 2.7.0 185 | 186 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 187 | • Android SDK at /Users/matthew/Library/Android/sdk 188 | • Android NDK location not configured (optional; useful for native profiling support) 189 | • Platform android-29, build-tools 28.0.3 190 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 191 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 192 | • All Android licenses accepted. 193 | 194 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 195 | • Xcode at /Applications/Xcode.app/Contents/Developer 196 | • Xcode 11.3.1, Build version 11C504 197 | • CocoaPods version 1.8.4 198 | 199 | [✓] Android Studio (version 3.4) 200 | • Android Studio at /Applications/Android Studio.app/Contents 201 | • Flutter plugin version 35.2.1 202 | • Dart plugin version 183.6270 203 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 204 | 205 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 206 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 207 | • Flutter plugin version 44.0.3 208 | • Dart plugin version 193.6494.35 209 | 210 | [✓] VS Code (version 1.41.1) 211 | • VS Code at /Applications/Visual Studio Code.app/Contents 212 | • Flutter extension version 3.4.1 213 | 214 | [!] Connected device 215 | ! No devices available 216 | 217 | ! Doctor found issues in 1 category. 218 | ``` 219 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_send_a_keyboard_action/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: MyForm(), 11 | ), 12 | ); 13 | } 14 | } 15 | 16 | class MyForm extends StatefulWidget { 17 | @override 18 | _MyFormState createState() => _MyFormState(); 19 | } 20 | 21 | class _MyFormState extends State { 22 | // We are creating controllers and focus nodes for our two text fields 23 | TextEditingController firstNameController; 24 | FocusNode firstNameFocusNode; 25 | 26 | TextEditingController lastNameController; 27 | FocusNode lastNameFocusNode; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | firstNameController = TextEditingController(); 33 | firstNameFocusNode = FocusNode(); 34 | 35 | lastNameController = TextEditingController(); 36 | lastNameFocusNode = FocusNode(); 37 | } 38 | 39 | // and we are cleaning up after ourselves by disposing the controllers and focus nodes 40 | @override 41 | void dispose() { 42 | firstNameController.dispose(); 43 | firstNameFocusNode.dispose(); 44 | 45 | lastNameController.dispose(); 46 | lastNameFocusNode.dispose(); 47 | super.dispose(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Column( 53 | children: [ 54 | TextField( 55 | decoration: InputDecoration(labelText: "First name"), 56 | textInputAction: TextInputAction.next, 57 | controller: firstNameController, 58 | focusNode: firstNameFocusNode, 59 | // When a user hits 'next' on the keyboard, it will unfocus firstName and focus lastName 60 | onSubmitted: (String _firstName) { 61 | firstNameFocusNode.unfocus(); 62 | FocusScope.of(context).requestFocus(lastNameFocusNode); 63 | }, 64 | ), 65 | TextField( 66 | decoration: InputDecoration(labelText: "Last name"), 67 | textInputAction: TextInputAction.done, 68 | controller: lastNameController, 69 | focusNode: lastNameFocusNode, 70 | // When a user hits 'done' on the keyboard, it will unfocus lastName and then show a SnackBar with the full name 71 | onSubmitted: (String _lastName) { 72 | lastNameFocusNode.unfocus(); 73 | Scaffold.of(context).showSnackBar(SnackBar( 74 | content: Text( 75 | "Your name is: ${firstNameController.text} ${lastNameController.text}"), 76 | )); 77 | }, 78 | ), 79 | ], 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_send_a_keyboard_action/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_send_a_keyboard_action/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:ftc/main.dart'; 4 | 5 | void main() { 6 | testWidgets("How do I send a keyboard event?", (WidgetTester tester) async { 7 | await tester.pumpWidget(MyApp()); 8 | 9 | // First, let's confirm we have two TextFields on the page 10 | Finder firstNameFinder = find.byType(TextField).at(0); 11 | Finder lastNameFinder = find.byType(TextField).at(1); 12 | 13 | expect(firstNameFinder, findsOneWidget); 14 | expect(lastNameFinder, findsOneWidget); 15 | 16 | // Next, let's enter our first name 17 | await tester.showKeyboard(firstNameFinder); 18 | tester.testTextInput.enterText("John"); 19 | 20 | // Tap the 'next' action in the keyboard 21 | await tester.testTextInput.receiveAction(TextInputAction.next); 22 | 23 | // This should automatically focus the last name 24 | // which means we don't need to show a new keyboard with a new finder 25 | tester.testTextInput.enterText("Doe"); 26 | 27 | // Tap the 'done' action in the keyboard 28 | await tester.testTextInput.receiveAction(TextInputAction.done); 29 | 30 | // This should open a SnackBar with the entered name 31 | await tester.pumpAndSettle(); 32 | expect(find.text("Your name is: John Doe"), findsOneWidget); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_animation/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_animation/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I test an animation? 4 | 5 | # Answer 6 | 7 | In this example, we will be taking a box, with a height of 0, and growing it to 100 over the course of 1000 milliseconds. Since flutter test doesn't automatically pump the widget, we can do it manually and check the box size along the way. 8 | 9 | ## 1) Create a flutter application. 10 | 11 | ```sh 12 | flutter create ftc 13 | cd ftc 14 | ``` 15 | 16 | ## 2) Get dependencies 17 | 18 | ```sh 19 | flutter packages get 20 | ``` 21 | 22 | ## 3) Replace the `lib/main.dart` file 23 | 24 | Replace the `lib/main.dart` file with the following: 25 | 26 | ```dart 27 | import 'package:flutter/material.dart'; 28 | 29 | void main() => runApp(MyApp()); 30 | 31 | class MyApp extends StatelessWidget { 32 | MyApp(); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return MaterialApp( 37 | home: Scaffold( 38 | body: Center( 39 | child: Body(), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | 46 | class Body extends StatefulWidget { 47 | Body(); 48 | 49 | @override 50 | _BodyState createState() => _BodyState(); 51 | } 52 | 53 | class _BodyState extends State with SingleTickerProviderStateMixin { 54 | Animation animation; 55 | AnimationController controller; 56 | 57 | @override 58 | void initState() { 59 | super.initState(); 60 | controller = AnimationController( 61 | vsync: this, 62 | duration: Duration(milliseconds: 1000), 63 | ); 64 | animation = Tween(begin: 0, end: 100).animate(controller); 65 | } 66 | 67 | @override 68 | void dispose() { 69 | controller.dispose(); 70 | super.dispose(); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return Column( 76 | children: [ 77 | RaisedButton( 78 | onPressed: () { 79 | controller.forward(); 80 | }, 81 | child: Text("Start animation"), 82 | ), 83 | AnimatedBuilder( 84 | animation: animation, 85 | builder: (BuildContext _context, _child) { 86 | return Container( 87 | key: Key("animatedBox"), 88 | height: animation.value, 89 | width: animation.value, 90 | decoration: BoxDecoration( 91 | border: Border.all( 92 | color: Colors.black, 93 | width: 2, 94 | ), 95 | ), 96 | ); 97 | }, 98 | ) 99 | ], 100 | ); 101 | } 102 | } 103 | ``` 104 | 105 | ## 4) Replace the `test/widget_test.dart` file. 106 | 107 | Replace the `test/widget_test.dart` file with the following: 108 | 109 | ```dart 110 | import 'package:flutter/material.dart'; 111 | import 'package:flutter/rendering.dart'; 112 | import 'package:flutter_test/flutter_test.dart'; 113 | import 'package:ftc/main.dart'; 114 | 115 | void main() { 116 | testWidgets("How do I test an animation?", (WidgetTester tester) async { 117 | await tester.pumpWidget(MyApp()); 118 | 119 | Finder startAnimationFinder = find.text("Start animation"); 120 | Finder animatedBoxFinder = find.byKey(Key("animatedBox")); 121 | 122 | expect(startAnimationFinder, findsOneWidget); 123 | expect(animatedBoxFinder, findsOneWidget); 124 | 125 | // The box starts off with a height of 0 126 | RenderConstrainedBox animatedBox = tester.renderObject(animatedBoxFinder); 127 | expect(animatedBox.size.height, 0); 128 | 129 | // Once we start the animation 130 | await tester.tap(startAnimationFinder); 131 | await tester.pump(); 132 | 133 | // and wait 100 milliseconds 134 | await tester.pump(Duration(milliseconds: 100)); 135 | // we expect the height to grow from 0 to 10 136 | expect(animatedBox.size.height, 10); 137 | 138 | // after another 100 milliseconds it should grow to 20 139 | await tester.pump(Duration(milliseconds: 100)); 140 | expect(animatedBox.size.height, 20); 141 | 142 | await tester.pump(Duration(milliseconds: 100)); 143 | // and then 30 144 | expect(animatedBox.size.height, 30); 145 | 146 | await tester.pump(Duration(milliseconds: 100)); 147 | // and then 40 148 | expect(animatedBox.size.height, 40); 149 | 150 | await tester.pump(Duration(milliseconds: 100)); 151 | // and then 50 152 | expect(animatedBox.size.height, 50); 153 | 154 | await tester.pump(Duration(milliseconds: 500)); 155 | // and then after another 500 milliseconds, the animation should be 156 | // complete and be a total height of 100 157 | expect(animatedBox.size.height, 100); 158 | }); 159 | } 160 | ``` 161 | 162 | 163 | ## 5) Run the flutter tests 164 | 165 | ```sh 166 | flutter test 167 | ``` 168 | 169 | # Run tests from example code in cookbook itself 170 | 171 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 172 | 173 | ```sh 174 | flutter create . 175 | flutter packages get 176 | flutter test 177 | ``` 178 | 179 | # Outputs when recipe was made / updated 180 | 181 | ## Output of running flutter tests 182 | 183 | ```sh 184 | $ flutter test 185 | 00:02 +1: All tests passed! 186 | ``` 187 | 188 | ## Output of running `flutter doctor -v` 189 | 190 | ```sh 191 | $ flutter doctor -v 192 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 193 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 194 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 195 | • Engine revision 2994f7e1e6 196 | • Dart version 2.7.0 197 | 198 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 199 | • Android SDK at /Users/matthew/Library/Android/sdk 200 | • Android NDK location not configured (optional; useful for native profiling support) 201 | • Platform android-29, build-tools 28.0.3 202 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 203 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 204 | • All Android licenses accepted. 205 | 206 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 207 | • Xcode at /Applications/Xcode.app/Contents/Developer 208 | • Xcode 11.3.1, Build version 11C504 209 | • CocoaPods version 1.8.4 210 | 211 | [✓] Android Studio (version 3.4) 212 | • Android Studio at /Applications/Android Studio.app/Contents 213 | • Flutter plugin version 35.2.1 214 | • Dart plugin version 183.6270 215 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 216 | 217 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 218 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 219 | • Flutter plugin version 44.0.3 220 | • Dart plugin version 193.6494.35 221 | 222 | [✓] VS Code (version 1.41.1) 223 | • VS Code at /Applications/Visual Studio Code.app/Contents 224 | • Flutter extension version 3.4.1 225 | 226 | [!] Connected device 227 | • Device 9C251FFBA00174 is not authorized. 228 | You might need to check your device for an authorization dialog. 229 | 230 | ! Doctor found issues in 1 category. 231 | ``` 232 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_animation/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | MyApp(); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | home: Scaffold( 12 | body: Center( 13 | child: Body(), 14 | ), 15 | ), 16 | ); 17 | } 18 | } 19 | 20 | class Body extends StatefulWidget { 21 | Body(); 22 | 23 | @override 24 | _BodyState createState() => _BodyState(); 25 | } 26 | 27 | class _BodyState extends State with SingleTickerProviderStateMixin { 28 | Animation animation; 29 | AnimationController controller; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | controller = AnimationController( 35 | vsync: this, 36 | duration: Duration(milliseconds: 1000), 37 | ); 38 | animation = Tween(begin: 0, end: 100).animate(controller); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | controller.dispose(); 44 | super.dispose(); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Column( 50 | children: [ 51 | RaisedButton( 52 | onPressed: () { 53 | controller.forward(); 54 | }, 55 | child: Text("Start animation"), 56 | ), 57 | AnimatedBuilder( 58 | animation: animation, 59 | builder: (BuildContext _context, _child) { 60 | return Container( 61 | key: Key("animatedBox"), 62 | height: animation.value, 63 | width: animation.value, 64 | decoration: BoxDecoration( 65 | border: Border.all( 66 | color: Colors.black, 67 | width: 2, 68 | ), 69 | ), 70 | ); 71 | }, 72 | ) 73 | ], 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_animation/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_animation/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:ftc/main.dart'; 5 | 6 | void main() { 7 | testWidgets("How do I test an animation?", (WidgetTester tester) async { 8 | await tester.pumpWidget(MyApp()); 9 | 10 | Finder startAnimationFinder = find.text("Start animation"); 11 | Finder animatedBoxFinder = find.byKey(Key("animatedBox")); 12 | 13 | expect(startAnimationFinder, findsOneWidget); 14 | expect(animatedBoxFinder, findsOneWidget); 15 | 16 | // The box starts off with a height of 0 17 | RenderConstrainedBox animatedBox = tester.renderObject(animatedBoxFinder); 18 | expect(animatedBox.size.height, 0); 19 | 20 | // Once we start the animation 21 | await tester.tap(startAnimationFinder); 22 | await tester.pump(); 23 | 24 | // and wait 100 milliseconds 25 | await tester.pump(Duration(milliseconds: 100)); 26 | // we expect the height to grow from 0 to 10 27 | expect(animatedBox.size.height, 10); 28 | 29 | // after another 100 milliseconds it should grow to 20 30 | await tester.pump(Duration(milliseconds: 100)); 31 | expect(animatedBox.size.height, 20); 32 | 33 | await tester.pump(Duration(milliseconds: 100)); 34 | // and then 30 35 | expect(animatedBox.size.height, 30); 36 | 37 | await tester.pump(Duration(milliseconds: 100)); 38 | // and then 40 39 | expect(animatedBox.size.height, 40); 40 | 41 | await tester.pump(Duration(milliseconds: 100)); 42 | // and then 50 43 | expect(animatedBox.size.height, 50); 44 | 45 | await tester.pump(Duration(milliseconds: 500)); 46 | // and then after another 500 milliseconds, the animation should be 47 | // complete and be a total height of 100 48 | expect(animatedBox.size.height, 100); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_exception/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_exception/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I test an Exception? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Get dependencies 15 | 16 | ```sh 17 | flutter packages get 18 | ``` 19 | 20 | ## 3) Replace the `lib/main.dart` file 21 | 22 | Replace the `lib/main.dart` file with the following: 23 | 24 | ```dart 25 | import 'package:flutter/material.dart'; 26 | 27 | void main() => runApp(MyApp()); 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | home: Scaffold( 34 | body: Column(children: [ 35 | RaisedButton( 36 | child: Text("Throw Exception"), 37 | onPressed: () { 38 | throw Exception(); 39 | }, 40 | ), 41 | RaisedButton( 42 | child: Text("Throw MyCustomException"), 43 | onPressed: () { 44 | throw MyCustomException(); 45 | }, 46 | ), 47 | ]), 48 | ), 49 | ); 50 | } 51 | } 52 | 53 | class MyCustomException implements Exception { 54 | MyCustomException(); 55 | } 56 | ``` 57 | 58 | ## 4) Replace the `test/widget_test.dart` file. 59 | 60 | Replace the `test/widget_test.dart` file with the following: 61 | 62 | ```dart 63 | import 'package:flutter/material.dart'; 64 | import 'package:flutter_test/flutter_test.dart'; 65 | 66 | import 'package:ftc/main.dart'; 67 | 68 | void main() { 69 | testWidgets("How do I test for an exception?", (WidgetTester tester) async { 70 | await tester.pumpWidget(MyApp()); 71 | 72 | Finder exceptionButtonFinder = find.byType(RaisedButton).at(0); 73 | Finder customExceptionButtonFinder = find.byType(RaisedButton).at(1); 74 | 75 | await tester.tap(exceptionButtonFinder); 76 | Exception exception = tester.takeException(); 77 | expect(exception, isException); 78 | 79 | await tester.tap(customExceptionButtonFinder); 80 | Exception customException = tester.takeException(); 81 | expect(customException, isInstanceOf()); 82 | }); 83 | } 84 | ``` 85 | 86 | 87 | ## 5) Run the flutter tests 88 | 89 | ```sh 90 | flutter test 91 | ``` 92 | 93 | # Run tests from example code in cookbook itself 94 | 95 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 96 | 97 | ```sh 98 | flutter create . 99 | flutter packages get 100 | flutter test 101 | ``` 102 | 103 | # Outputs when recipe was made / updated 104 | 105 | ## Output of running flutter tests 106 | 107 | ```sh 108 | $ flutter test 109 | 00:02 +1: All tests passed! 110 | ``` 111 | 112 | ## Output of running `flutter doctor -v` 113 | 114 | ```sh 115 | $ flutter doctor -v 116 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 117 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 118 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 119 | • Engine revision 2994f7e1e6 120 | • Dart version 2.7.0 121 | 122 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 123 | • Android SDK at /Users/matthew/Library/Android/sdk 124 | • Android NDK location not configured (optional; useful for native profiling support) 125 | • Platform android-29, build-tools 28.0.3 126 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 127 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 128 | • All Android licenses accepted. 129 | 130 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 131 | • Xcode at /Applications/Xcode.app/Contents/Developer 132 | • Xcode 11.3.1, Build version 11C504 133 | • CocoaPods version 1.8.4 134 | 135 | [✓] Android Studio (version 3.4) 136 | • Android Studio at /Applications/Android Studio.app/Contents 137 | • Flutter plugin version 35.2.1 138 | • Dart plugin version 183.6270 139 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 140 | 141 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 142 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 143 | • Flutter plugin version 44.0.3 144 | • Dart plugin version 193.6494.35 145 | 146 | [✓] VS Code (version 1.41.1) 147 | • VS Code at /Applications/Visual Studio Code.app/Contents 148 | • Flutter extension version 3.4.1 149 | 150 | [✓] Connected device (1 available) 151 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 152 | 153 | • No issues found! 154 | ``` 155 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_exception/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | home: Scaffold( 10 | body: Column(children: [ 11 | RaisedButton( 12 | child: Text("Throw Exception"), 13 | onPressed: () { 14 | throw Exception(); 15 | }, 16 | ), 17 | RaisedButton( 18 | child: Text("Throw MyCustomException"), 19 | onPressed: () { 20 | throw MyCustomException(); 21 | }, 22 | ), 23 | ]), 24 | ), 25 | ); 26 | } 27 | } 28 | 29 | class MyCustomException implements Exception { 30 | MyCustomException(); 31 | } 32 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_exception/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_an_exception/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:ftc/main.dart'; 5 | 6 | void main() { 7 | testWidgets("How do I test for an exception?", (WidgetTester tester) async { 8 | await tester.pumpWidget(MyApp()); 9 | 10 | Finder exceptionButtonFinder = find.byType(RaisedButton).at(0); 11 | Finder customExceptionButtonFinder = find.byType(RaisedButton).at(1); 12 | 13 | await tester.tap(exceptionButtonFinder); 14 | Exception exception = tester.takeException(); 15 | expect(exception, isException); 16 | 17 | await tester.tap(customExceptionButtonFinder); 18 | Exception customException = tester.takeException(); 19 | expect(customException, isInstanceOf()); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_routes/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_routes/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | How do I test routes? 4 | 5 | # Answer 6 | 7 | ## 1) Create a flutter application. 8 | 9 | ```sh 10 | flutter create ftc 11 | cd ftc 12 | ``` 13 | 14 | ## 2) Get dependencies 15 | 16 | ```sh 17 | flutter packages get 18 | ``` 19 | 20 | ## 3) Replace the `lib/main.dart` file 21 | 22 | Replace the `lib/main.dart` file with the following: 23 | 24 | ```dart 25 | import 'package:flutter/material.dart'; 26 | 27 | void main() => runApp(MyApp()); 28 | 29 | class MyApp extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return MaterialApp( 33 | initialRoute: "/page1", 34 | routes: { 35 | "/page1": (BuildContext context) => Page1(), 36 | "/page2": (BuildContext context) => Page2(), 37 | "/page3": (BuildContext context) => Page3(), 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | class Page1 extends StatelessWidget { 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: AppBar( 48 | title: Text("Page 1"), 49 | ), 50 | body: Column( 51 | children: [ 52 | RaisedButton( 53 | onPressed: () => Navigator.of(context).pushNamed("/page2"), 54 | child: Text("pushNamed to page2"), 55 | ), 56 | RaisedButton( 57 | onPressed: () => 58 | Navigator.of(context).pushReplacementNamed("/page2"), 59 | child: Text("pushReplacementNamed to page2"), 60 | ) 61 | ], 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | class Page2 extends StatelessWidget { 68 | @override 69 | Widget build(BuildContext context) { 70 | return Scaffold( 71 | appBar: AppBar( 72 | title: Text("Page 2"), 73 | ), 74 | body: Column( 75 | children: [ 76 | RaisedButton( 77 | onPressed: () => Navigator.of(context).pushNamed("/page3"), 78 | child: Text("pushNamed to page3"), 79 | ), 80 | ], 81 | ), 82 | ); 83 | } 84 | } 85 | 86 | class Page3 extends StatelessWidget { 87 | @override 88 | Widget build(BuildContext context) { 89 | return Scaffold( 90 | appBar: AppBar( 91 | title: Text("Page 3"), 92 | ), 93 | body: Column( 94 | children: [ 95 | RaisedButton( 96 | onPressed: () => Navigator.of(context).pop(), 97 | child: Text("pop"), 98 | ), 99 | RaisedButton( 100 | onPressed: () => Navigator.of(context).popUntil((Route route) { 101 | return route.settings.name == "/page1"; 102 | }), 103 | child: Text("popUntil page1"), 104 | ) 105 | ], 106 | ), 107 | ); 108 | } 109 | } 110 | ``` 111 | 112 | ## 4) Replace the `test/widget_test.dart` file. 113 | 114 | Replace the `test/widget_test.dart` file with the following: 115 | 116 | ```dart 117 | import 'package:flutter_test/flutter_test.dart'; 118 | import 'package:ftc/main.dart'; 119 | 120 | void main() { 121 | testWidgets("How do I test routes?", (WidgetTester tester) async { 122 | await tester.pumpWidget(MyApp()); 123 | 124 | // First, let's make sure we are on /page1 125 | expect(find.byType(Page1), findsOneWidget); 126 | expect(find.byType(Page2), findsNothing); 127 | expect(find.byType(Page3), findsNothing); 128 | 129 | // 130 | // Next, let's navigate to /page2 131 | // 132 | await tester.tap(find.text("pushNamed to page2")); 133 | await tester.pumpAndSettle(); 134 | 135 | // Confirm we are on /page2 136 | expect(find.byType(Page1), findsNothing); 137 | expect(find.byType(Page2), findsOneWidget); 138 | expect(find.byType(Page3), findsNothing); 139 | 140 | // However, since routes are in a stack, and we pushed page2 on top of page1, 141 | // we should confirm that page1 still exists. 142 | // We can do this by setting `skipOffstage` to false. 143 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 144 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 145 | expect(find.byType(Page3, skipOffstage: false), findsNothing); 146 | 147 | // 148 | // Next, let's navigate to /page3 149 | // 150 | 151 | await tester.tap(find.text("pushNamed to page3")); 152 | await tester.pumpAndSettle(); 153 | 154 | // Confirm we are on /page3 155 | expect(find.byType(Page1), findsNothing); 156 | expect(find.byType(Page2), findsNothing); 157 | expect(find.byType(Page3), findsOneWidget); 158 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 159 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 160 | expect(find.byType(Page3, skipOffstage: false), findsOneWidget); 161 | 162 | // 163 | // Next, let's navigate back to /page2 164 | // 165 | 166 | await tester.tap(find.text("pop")); 167 | await tester.pumpAndSettle(); 168 | 169 | // confirm we are on /page2 170 | expect(find.byType(Page1), findsNothing); 171 | expect(find.byType(Page2), findsOneWidget); 172 | expect(find.byType(Page3), findsNothing); 173 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 174 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 175 | expect(find.byType(Page3, skipOffstage: false), findsNothing); 176 | 177 | // 178 | // Finally, let's navigate back to /page3 and then popUntil we are on /page1 179 | // 180 | 181 | await tester.tap(find.text("pushNamed to page3")); 182 | await tester.pumpAndSettle(); 183 | 184 | // Confirm we are on /page3 185 | expect(find.byType(Page1), findsNothing); 186 | expect(find.byType(Page2), findsNothing); 187 | expect(find.byType(Page3), findsOneWidget); 188 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 189 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 190 | expect(find.byType(Page3, skipOffstage: false), findsOneWidget); 191 | 192 | await tester.tap(find.text("popUntil page1")); 193 | await tester.pumpAndSettle(); 194 | 195 | // Confirm we are on /page1 196 | expect(find.byType(Page1), findsOneWidget); 197 | expect(find.byType(Page2), findsNothing); 198 | expect(find.byType(Page3), findsNothing); 199 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 200 | expect(find.byType(Page2, skipOffstage: false), findsNothing); 201 | expect(find.byType(Page3, skipOffstage: false), findsNothing); 202 | }); 203 | } 204 | ``` 205 | 206 | 207 | ## 5) Run the flutter tests 208 | 209 | ```sh 210 | flutter test 211 | ``` 212 | 213 | # Run tests from example code in cookbook itself 214 | 215 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 216 | 217 | ```sh 218 | flutter create . 219 | flutter packages get 220 | flutter test 221 | ``` 222 | 223 | # Outputs when recipe was made / updated 224 | 225 | ## Output of running flutter tests 226 | 227 | ```sh 228 | $ flutter test 229 | 00:02 +1: All tests passed! 230 | ``` 231 | 232 | ## Output of running `flutter doctor -v` 233 | 234 | ```sh 235 | $ flutter doctor -v 236 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 237 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 238 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 239 | • Engine revision 2994f7e1e6 240 | • Dart version 2.7.0 241 | 242 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 243 | • Android SDK at /Users/matthew/Library/Android/sdk 244 | • Android NDK location not configured (optional; useful for native profiling support) 245 | • Platform android-29, build-tools 28.0.3 246 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 247 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 248 | • All Android licenses accepted. 249 | 250 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 251 | • Xcode at /Applications/Xcode.app/Contents/Developer 252 | • Xcode 11.3.1, Build version 11C504 253 | • CocoaPods version 1.8.4 254 | 255 | [✓] Android Studio (version 3.4) 256 | • Android Studio at /Applications/Android Studio.app/Contents 257 | • Flutter plugin version 35.2.1 258 | • Dart plugin version 183.6270 259 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 260 | 261 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 262 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 263 | • Flutter plugin version 44.0.3 264 | • Dart plugin version 193.6494.35 265 | 266 | [✓] VS Code (version 1.41.1) 267 | • VS Code at /Applications/Visual Studio Code.app/Contents 268 | • Flutter extension version 3.4.1 269 | 270 | [✓] Connected device (1 available) 271 | • iPhone 8 • AD7A90EB-5E73-427E-B9B7-DD3B07E2FEF1 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator) 272 | 273 | • No issues found! 274 | ``` 275 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_routes/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | initialRoute: "/page1", 10 | routes: { 11 | "/page1": (BuildContext context) => Page1(), 12 | "/page2": (BuildContext context) => Page2(), 13 | "/page3": (BuildContext context) => Page3(), 14 | }, 15 | ); 16 | } 17 | } 18 | 19 | class Page1 extends StatelessWidget { 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: Text("Page 1"), 25 | ), 26 | body: Column( 27 | children: [ 28 | RaisedButton( 29 | onPressed: () => Navigator.of(context).pushNamed("/page2"), 30 | child: Text("pushNamed to page2"), 31 | ), 32 | RaisedButton( 33 | onPressed: () => 34 | Navigator.of(context).pushReplacementNamed("/page2"), 35 | child: Text("pushReplacementNamed to page2"), 36 | ) 37 | ], 38 | ), 39 | ); 40 | } 41 | } 42 | 43 | class Page2 extends StatelessWidget { 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: AppBar( 48 | title: Text("Page 2"), 49 | ), 50 | body: Column( 51 | children: [ 52 | RaisedButton( 53 | onPressed: () => Navigator.of(context).pushNamed("/page3"), 54 | child: Text("pushNamed to page3"), 55 | ), 56 | ], 57 | ), 58 | ); 59 | } 60 | } 61 | 62 | class Page3 extends StatelessWidget { 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: AppBar( 67 | title: Text("Page 3"), 68 | ), 69 | body: Column( 70 | children: [ 71 | RaisedButton( 72 | onPressed: () => Navigator.of(context).pop(), 73 | child: Text("pop"), 74 | ), 75 | RaisedButton( 76 | onPressed: () => Navigator.of(context).popUntil((Route route) { 77 | return route.settings.name == "/page1"; 78 | }), 79 | child: Text("popUntil page1"), 80 | ) 81 | ], 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_routes/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /recipes/flutter_test/how_do_i_test_routes/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:ftc/main.dart'; 3 | 4 | void main() { 5 | testWidgets("How do I test routes?", (WidgetTester tester) async { 6 | await tester.pumpWidget(MyApp()); 7 | 8 | // First, let's make sure we are on /page1 9 | expect(find.byType(Page1), findsOneWidget); 10 | expect(find.byType(Page2), findsNothing); 11 | expect(find.byType(Page3), findsNothing); 12 | 13 | // 14 | // Next, let's navigate to /page2 15 | // 16 | await tester.tap(find.text("pushNamed to page2")); 17 | await tester.pumpAndSettle(); 18 | 19 | // Confirm we are on /page2 20 | expect(find.byType(Page1), findsNothing); 21 | expect(find.byType(Page2), findsOneWidget); 22 | expect(find.byType(Page3), findsNothing); 23 | 24 | // However, since routes are in a stack, and we pushed page2 on top of page1, 25 | // we should confirm that page1 still exists. 26 | // We can do this by setting `skipOffstage` to false. 27 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 28 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 29 | expect(find.byType(Page3, skipOffstage: false), findsNothing); 30 | 31 | // 32 | // Next, let's navigate to /page3 33 | // 34 | 35 | await tester.tap(find.text("pushNamed to page3")); 36 | await tester.pumpAndSettle(); 37 | 38 | // Confirm we are on /page3 39 | expect(find.byType(Page1), findsNothing); 40 | expect(find.byType(Page2), findsNothing); 41 | expect(find.byType(Page3), findsOneWidget); 42 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 43 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 44 | expect(find.byType(Page3, skipOffstage: false), findsOneWidget); 45 | 46 | // 47 | // Next, let's navigate back to /page2 48 | // 49 | 50 | await tester.tap(find.text("pop")); 51 | await tester.pumpAndSettle(); 52 | 53 | // confirm we are on /page2 54 | expect(find.byType(Page1), findsNothing); 55 | expect(find.byType(Page2), findsOneWidget); 56 | expect(find.byType(Page3), findsNothing); 57 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 58 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 59 | expect(find.byType(Page3, skipOffstage: false), findsNothing); 60 | 61 | // 62 | // Finally, let's navigate back to /page3 and then popUntil we are on /page1 63 | // 64 | 65 | await tester.tap(find.text("pushNamed to page3")); 66 | await tester.pumpAndSettle(); 67 | 68 | // Confirm we are on /page3 69 | expect(find.byType(Page1), findsNothing); 70 | expect(find.byType(Page2), findsNothing); 71 | expect(find.byType(Page3), findsOneWidget); 72 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 73 | expect(find.byType(Page2, skipOffstage: false), findsOneWidget); 74 | expect(find.byType(Page3, skipOffstage: false), findsOneWidget); 75 | 76 | await tester.tap(find.text("popUntil page1")); 77 | await tester.pumpAndSettle(); 78 | 79 | // Confirm we are on /page1 80 | expect(find.byType(Page1), findsOneWidget); 81 | expect(find.byType(Page2), findsNothing); 82 | expect(find.byType(Page3), findsNothing); 83 | expect(find.byType(Page1, skipOffstage: false), findsOneWidget); 84 | expect(find.byType(Page2, skipOffstage: false), findsNothing); 85 | expect(find.byType(Page3, skipOffstage: false), findsNothing); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /recipes/flutter_test/what_if_i_need_build_context/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # Flutter Test Cookbook (flutter_test) 40 | .dart_tool/ 41 | .metadata 42 | .packages 43 | android/ 44 | ios/ 45 | pubspec.lock 46 | ftc.iml 47 | *.*~ 48 | test_driver/ -------------------------------------------------------------------------------- /recipes/flutter_test/what_if_i_need_build_context/README.md: -------------------------------------------------------------------------------- 1 | # Question 2 | 3 | What do I do if I need a [BuildContext](https://api.flutter.dev/flutter/widgets/BuildContext-class.html)? 4 | 5 | # Answer 6 | 7 | There are several reasons we might _want_ to use a BuildContext in our tests (it is a matter of opinion if we actually should). 8 | 9 | There are two main methods to get a BuildContext when using flutter_test. 10 | 11 | a) Finding an [Element](https://api.flutter.dev/flutter/widgets/Element-class.html) and taking advantage of the fact that [an Element can be used as a BuildContext](https://www.reddit.com/r/Flutter/comments/bcmj70/please_tell_me_simply_what_is_context_and_build/eldf63d?utm_source=share&utm_medium=web2x) 12 | 13 | b) Using a [GlobalKey](https://api.flutter.dev/flutter/widgets/GlobalKey-class.html) 14 | 15 | For the sake of this recipe, we are going to use a _contrived_ example. We have two pages, Page1 and Page2, and we want to navigate between them in our tests using a [Navigator](https://api.flutter.dev/flutter/dart-html/Navigator-class.html). 16 | 17 | ## 1) Create a flutter application. 18 | 19 | ```sh 20 | flutter create ftc 21 | cd ftc 22 | ``` 23 | 24 | ## 2) Get dependencies 25 | 26 | ```sh 27 | flutter packages get 28 | ``` 29 | 30 | ## 3) Replace the `lib/main.dart` file 31 | 32 | Replace the `lib/main.dart` file with the following: 33 | 34 | ```dart 35 | import 'package:flutter/material.dart'; 36 | 37 | void main() => runApp(MyApp()); 38 | 39 | class MyApp extends StatelessWidget { 40 | @override 41 | Widget build(BuildContext context) { 42 | return MaterialApp( 43 | initialRoute: "/page1", 44 | routes: { 45 | "/page1": (BuildContext context) => Page1(), 46 | "/page2": (BuildContext context) => Page2(), 47 | }, 48 | ); 49 | } 50 | } 51 | 52 | class Page1 extends StatelessWidget { 53 | @override 54 | Widget build(BuildContext context) { 55 | return Scaffold( 56 | body: Center(child: Text("Page 1")), 57 | ); 58 | } 59 | } 60 | 61 | class Page2 extends StatelessWidget { 62 | @override 63 | Widget build(BuildContext context) { 64 | return Scaffold( 65 | body: Center(child: Text("Page 2")), 66 | ); 67 | } 68 | } 69 | ``` 70 | 71 | ## 4) Replace the `test/widget_test.dart` file. 72 | 73 | Replace the `test/widget_test.dart` file with the following: 74 | 75 | ```dart 76 | import 'package:flutter/material.dart'; 77 | import 'package:flutter_test/flutter_test.dart'; 78 | 79 | import 'package:ftc/main.dart'; 80 | 81 | void main() { 82 | testWidgets("What if I need a build context?", (WidgetTester tester) async { 83 | await tester.pumpWidget(MyApp()); 84 | Finder page1Finder = find.byType(Page1); 85 | Finder page2Finder = find.byType(Page2); 86 | 87 | // Since the initial route was for /page1 88 | // we expect to see a [Page1] widget and not a [Page2] widget 89 | expect(page1Finder, findsOneWidget); 90 | expect(page2Finder, findsNothing); 91 | 92 | // 93 | // Method a) 94 | // 95 | 96 | // get [BuildContext] from an element 97 | BuildContext page1BuildContext = tester.element(page1Finder); 98 | 99 | // find the closest [Navigator] (which is made available because of [MaterialApp]) 100 | // and pushNamed to /page2 101 | Navigator.of(page1BuildContext).pushNamed("/page2"); 102 | 103 | // Note, since we are using a [MaterialApp] and its named routes use the [MaterialPageRoute], 104 | // there is a transition of 300 ms 105 | // https://github.com/flutter/flutter/blob/e58dc16d7bec7199190f1408667e24e38328cc3b/packages/flutter/lib/src/material/page.dart#L61 106 | // ... so we will need to pump until that transition has settled 107 | await tester.pumpAndSettle(); 108 | 109 | // We now expect to be on [Page2] 110 | expect(page1Finder, findsNothing); 111 | expect(page2Finder, findsOneWidget); 112 | 113 | // 114 | // Method b) 115 | // 116 | 117 | // get [BuildContext] from a [GlobalKey] 118 | BuildContext page2BuildContext = page2ScaffoldKey.currentContext; 119 | 120 | // Let's navigate back to [Page1] 121 | Navigator.of(page2BuildContext).pushNamed("/page1"); 122 | await tester.pumpAndSettle(); 123 | 124 | // We now expect to be on [Page1] again 125 | expect(page1Finder, findsOneWidget); 126 | expect(page2Finder, findsNothing); 127 | }); 128 | } 129 | ``` 130 | 131 | 132 | ## 5) Run the flutter tests 133 | 134 | ```sh 135 | flutter test 136 | ``` 137 | 138 | # Run tests from example code in cookbook itself 139 | 140 | If you have cloned [flutter_test_cookbook](https://github.com/gadfly361/flutter_test_cookbook/tree/master) and simply want to run the tests from this recipe, then: 141 | 142 | ```sh 143 | flutter create . 144 | flutter packages get 145 | flutter test 146 | ``` 147 | 148 | # Outputs when recipe was made / updated 149 | 150 | ## Output of running flutter tests 151 | 152 | ```sh 153 | $ flutter test 154 | 00:02 +1: All tests passed! 155 | ``` 156 | 157 | ## Output of running `flutter doctor -v` 158 | 159 | ```sh 160 | $ flutter doctor -v 161 | [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.15.2 19C57, locale en-US) 162 | • Flutter version 1.12.13+hotfix.5 at /Users/matthew/gadfly/flutter_versions/flutter_1.12.13+hotfix.5 163 | • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800 164 | • Engine revision 2994f7e1e6 165 | • Dart version 2.7.0 166 | 167 | [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) 168 | • Android SDK at /Users/matthew/Library/Android/sdk 169 | • Android NDK location not configured (optional; useful for native profiling support) 170 | • Platform android-29, build-tools 28.0.3 171 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 172 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 173 | • All Android licenses accepted. 174 | 175 | [✓] Xcode - develop for iOS and macOS (Xcode 11.3.1) 176 | • Xcode at /Applications/Xcode.app/Contents/Developer 177 | • Xcode 11.3.1, Build version 11C504 178 | • CocoaPods version 1.8.4 179 | 180 | [✓] Android Studio (version 3.4) 181 | • Android Studio at /Applications/Android Studio.app/Contents 182 | • Flutter plugin version 35.2.1 183 | • Dart plugin version 183.6270 184 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01) 185 | 186 | [✓] IntelliJ IDEA Community Edition (version 2019.3.3) 187 | • IntelliJ at /Applications/IntelliJ IDEA CE.app 188 | • Flutter plugin version 44.0.3 189 | • Dart plugin version 193.6494.35 190 | 191 | [✓] VS Code (version 1.41.1) 192 | • VS Code at /Applications/Visual Studio Code.app/Contents 193 | • Flutter extension version 3.4.1 194 | 195 | [!] Connected device 196 | ! No devices available 197 | 198 | ! Doctor found issues in 1 category. 199 | ``` 200 | -------------------------------------------------------------------------------- /recipes/flutter_test/what_if_i_need_build_context/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void main() => runApp(MyApp()); 4 | 5 | class MyApp extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return MaterialApp( 9 | initialRoute: "/page1", 10 | routes: { 11 | "/page1": (BuildContext context) => Page1(), 12 | "/page2": (BuildContext context) => Page2(), 13 | }, 14 | ); 15 | } 16 | } 17 | 18 | class Page1 extends StatelessWidget { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | body: Center(child: Text("Page 1")), 23 | ); 24 | } 25 | } 26 | 27 | GlobalKey page2ScaffoldKey = GlobalKey(); 28 | 29 | class Page2 extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | key: page2ScaffoldKey, 34 | body: Center(child: Text("Page 2")), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /recipes/flutter_test/what_if_i_need_build_context/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ftc 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | -------------------------------------------------------------------------------- /recipes/flutter_test/what_if_i_need_build_context/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:ftc/main.dart'; 5 | 6 | void main() { 7 | testWidgets("What if I need a build context?", (WidgetTester tester) async { 8 | await tester.pumpWidget(MyApp()); 9 | Finder page1Finder = find.byType(Page1); 10 | Finder page2Finder = find.byType(Page2); 11 | 12 | // Since the initial route was for /page1 13 | // we expect to see a [Page1] widget and not a [Page2] widget 14 | expect(page1Finder, findsOneWidget); 15 | expect(page2Finder, findsNothing); 16 | 17 | // 18 | // Method a) 19 | // 20 | 21 | // get [BuildContext] from an element 22 | BuildContext page1BuildContext = tester.element(page1Finder); 23 | 24 | // find the closest [Navigator] (which is made available because of [MaterialApp]) 25 | // and pushNamed to /page2 26 | Navigator.of(page1BuildContext).pushNamed("/page2"); 27 | 28 | // Note, since we are using a [MaterialApp] and its named routes use the [MaterialPageRoute], 29 | // there is a transition of 300 ms 30 | // https://github.com/flutter/flutter/blob/e58dc16d7bec7199190f1408667e24e38328cc3b/packages/flutter/lib/src/material/page.dart#L61 31 | // ... so we will need to pump until that transition has settled 32 | await tester.pumpAndSettle(); 33 | 34 | // We now expect to be on [Page2] 35 | expect(page1Finder, findsNothing); 36 | expect(page2Finder, findsOneWidget); 37 | 38 | // 39 | // Method b) 40 | // 41 | 42 | // get [BuildContext] from a [GlobalKey] 43 | BuildContext page2BuildContext = page2ScaffoldKey.currentContext; 44 | 45 | // Let's navigate back to [Page1] 46 | Navigator.of(page2BuildContext).pushNamed("/page1"); 47 | await tester.pumpAndSettle(); 48 | 49 | // We now expect to be on [Page1] again 50 | expect(page1Finder, findsOneWidget); 51 | expect(page2Finder, findsNothing); 52 | }); 53 | } 54 | --------------------------------------------------------------------------------