Flutter Tests made simple
Bugs can take a considerable time in software development, if proper tests are not implemented in a project, future code maintainability and new feature implementations become a problem.
“Typical bugs” are errors, flaws or faults in the design, development, or operation of computer software that causes it to produce an incorrect or unexpected result.
Most seasoned software developers eventually grow to become comfortable at navigating complex code and quick at fixing bugs, this is however made possible by writting proper test cases.
Programs may have bugs (unexpected behavior), programming languages may have design flaws or implementation bugs (In the case of the famous WAT).
Flutter tests are incredibly easy to write and handy when verifying the behavior of a single core function, method, or class. This is great for checking those external dependencies such as a Network, Location, Events or Database are working as expected and that the right result is provided.
What we will cover in this short meticulous article.
- How tests are written and structured in Flutter.
- Test execution and outputs.
- How to mock data and network requests.
To start, we begin by adding a test package inside your project pubspec.yaml under the dev dependencies
section. Flutter includes a test
folder on the root of the project with an example widget_test.dart
file.
Every file should begin with a main
method as an entry.
root_project/test/test.dart
The test()
method requires two parameters:
- Description: A string that describes the test actions implemented in the function, this could be a detailed text.
- Function: This is where the logic resides, after which we compare the result with our expectation. you need to import
package:test/test.dart
import 'package:test/test.dart';main(){ test('+ Button to increament by 1',(){
});}
The expect()
method asserts is the actual
value matches the matcher
value, if at one point the two values don't match the test will fail.
In case of failure, we can provide some extra information for code documentation purposes. Use detailed test descriptions, this makes it most likely to remember what each test does.
import 'package:test/test.dart';main(){
test('+ Button to increament by 1',(){ final addLogic = AddClass();
expect(addLogic.addByOne(1), 2, reason: 'Not exactly 2'); });
}
A single test can be run by just running
dart test path_to/test.dart
(as of Dart 2.10 - prior SDK versions must usepub run test
instead ofdart test
).
To run unit tests, we simply have to run the below command and if every test runs well, then we will get a message on the console indicating All tests passed.
You can select specific test cases to run by name using dart test -n "+ Button to increment by 1"
. this will interpret the string as a regular expression.
flutter test
For more options regarding unit tests, you can execute this command:
flutter test --help
Run tests using IntelliJ or VSCode
The Flutter plugins for IntelliJ and VSCode support running tests. This is often the best option while writing tests because it provides the fastest feedback loop as well as the ability to set breakpoints.
- IntelliJ
- Open the
test.dart
file - Select the
Run
menu - Click the
Run 'tests in test.dart'
option - Alternatively, use the appropriate keyboard shortcut for your platform.
- VSCode
- Open the
test.dart
file - Select the
Run
menu - Click the
Start Debugging
option - Alternatively, use the appropriate keyboard shortcut for your platform.
The test runner will consider any file that ends with _test.dart
in the test/
folder of your root directory to be a test file. If you don't pass any paths, it will run all the test files in that directory, making it easy to test your entire application at once.
In situations which are a bit complicated for validation tests and which may require you to go beyond just comparing two objects, Dart provides the Matcher library, which is inclusive in the test framework.
Matcher library includes essential matchers such as isNull,
isNotNull
,isEmpty
,isNotEmpty
and many more. A type matcher subclass to validate the type of our results. Read more about matchers on of matchers in matcher.dart page.
The group()
method allows tests to be grouped together, this is common in situations where the tests serve the same purpose/object.
main() { group('add logics tests', () { test('Add value by 1', () { }); test('Add value by 2', () { }); });}
Tests should be short and precise, this should make it easy for any developer to recognise the test functions at first glance. Long functions that require scrolling tend to be a bit messy.
The setUpAll()
method runs once before tests are executed.
The setUp()
method is called before every test in a group or a single test.
The tearDown()
method is called after each test even if the test has failed.
The tearDownAll()
method is executed after all tests have been completed.
You can use the
setUp()
andtearDown()
methods to share code between tests. ThesetUp()
callback will run before every test in a group or test suite, andtearDown()
will run after.tearDown()
will run even if a test fails, to ensure that it has a chance to clean up after itself.
Your code should continue to work as you build on more features or when you modify existing functionality, to achieve this tests have to be done during the development process, although does not mean the software has no bugs. It just implies that the software does not have any typical bugs.
Thank you for spending time reading this article, I hope you enjoyed what you read and learned something. If you have any feedback, kindly leave them in the comments.
Want to keep in touch online? Medium | Twitter | Instagram | Behance