Test-driven Development in Flutter

Test-driven Development in Flutter

Learn how to make your Flutter apps reliable with minimal bugs and reduce repetitive debugging by making your code more efficient.

Introduction

In this article, I will describe the steps of implementation of Test-driven Development (TDD) in your Flutter project and also cover a basic introduction to the subject.

What is TDD?

TDD or Test-driven Development is the practice of writing quality assurance test cases for each unit of the app before any feature or code is implemented.

Need for TDD

Test-driven development is beneficial because of the following advantages:

  1. Generates minimal Bugs
  2. Identifies bugs in the early phase
  3. Reduces repetitive debugging
  4. Makes code more reliable with refactoring

Types of Testing

Unit Testing

Unit testing is conducted to check the implementation or functionality of a single class, method, or function.

Widget Testing

Widget testing is conducted to check the UI or widgets and components used to build a screen.

Integration Testing

Integration tests are written to check the application's complete functionality. It is also called end-to-end or GUI testing.

Additional Types Of Testing

Security Testing

Security tests are meant to test the authentication, access, and code injections for the complete application. It makes an app that is ready to deploy highly secure.

Smoke Testing

Smoke testing is written in order to check the complete or core functionality of an application in a precise way.

Process of Test-driven Development

The process of Test-driven Development tends to be iterative and loopy (i.e., the process will be in a loop until the conditions or test cases that are mentioned are passed).

There are mainly three phases in TDD:

1. Red Phase In this phase, the developer needs to write test scenarios for features that need to be created. Initially, it will return errors as the features or widgets have not been created yet.

2. Green Phase The actual development of feature code or the creation of a widget is done in this phase. This step is done with or without optimal writing of code; it only ensures that the features are created and the test case is successful.

3. Refactor Phase Refactoring or optimization of the code written for both testing and actual feature code needs to be done in this phase.

Steps for Implementing TDD for your application

1. Folder Structure

  • Remove the test file created by the default Flutter project i.e the boilerplate test code widget_test.dart file in your Flutter app.
  • Create a sub-folder as per the requirements for your application. If you want to implement Test Driven Development, include all logic and UI components that go with creating subfolder structures domain layer, data layer, and presentation layer otherwise moves forward with creating single files for each case.
  • The test file should be created with the naming pattern screen_name_test.dart as shown in the image given below:

TDD_Folder.gif

2. Finalize tests you need for your application

  • Decide the type of tests you may need to add to your projects based on your requirements (data, domain and presentation or unit tests/widgets tests/integration/security tests)

3. Writing Test Cases

  • Here I have taken an example at_theme_flutter pub.dev package from the atsign foundation in order to demonstrate the implementation steps in detail:

Folder_TDD.png

  • As per the package requirement here we have decided to go with only widget testing as there is no need for any unit or security testing. So as a first step, we have created a folder for the same as per the naming conventions.

Testing a use case

Initially, we have taken a Color card widget. Here we need to write a test case in order to check whether the expected colors have been passed to the widget or not. Enter the code given below in your console:

void main() {
  Widget _wrapWidgetWithMaterialApp({required Widget colorCard}) {
    return TestMaterialApp(home: Builder(builder: (BuildContext context) {
      SizeConfig().init(context);
      return colorCard;
    }));
  }
import 'package:at_common_flutter/at_common_flutter.dart';
import 'package:at_theme_flutter/src/widgets/color_card.dart';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import '../test_material_app.dart';

void main() {
  Widget _wrapWidgetWithMaterialApp({required Widget colorCard}) {
    return TestMaterialApp(home: Builder(builder: (BuildContext context) {
      SizeConfig().init(context);
      return colorCard;
    }));
  }

  /// Functional test cases for Color Card Widget
  group('Color Card Widget Tests:', () {
    // Test Case to Check Color Card is displayed
    final colorCard = ColorCard(color: Colors.orange, isSelected: true);
    testWidgets("Color Card is displayed", (WidgetTester tester) async {
      await tester.pumpWidget(
          _wrapWidgetWithMaterialApp(colorCard: colorCard));
      expect(find.byType(ColorCard), findsOneWidget);
    });
  });
}

Here are some important elements:

  • group() => It takes in a group of tests. It also holds the parameter description and function body.
  • testWidget() =>It runs a callback in a Flutter test environment; the callback can be synchronous or asynchronous. It holds parameters like the description and function body.
  • tester.pumpWidget() => It is used to render the UI from widget given.
  • expect() => It is used to specify the expected behavior of the widget.

4. Run flutter test command

  • Enter the following command into your console:
flutter test
  • If your test case tends to pass all the cases then the Flutter test is considered successful, otherwise check the error, refactor the code, and re-run the command till all your test cases are passed.

Conclusion

Implementing Test-Driven Development into Flutter apps is reliable and minimizes bugs. On the other hand, it seems to be time-taking and increases the total lines of code. Using this technique to build your applications is hence purely dependent on your requirements and needs.

In this article, I have covered the basic introduction to TDD and you can experiment with the same by developing a simple UI-only app and writing test cases.

Note: Try out Mockito for writing unit tests with data and practise TDD with clean architecture.