Testing your Flutter app
Tags: testing, flutter, dart, programming
Published: 2023-02-02 20:11:29

Hello everybody, today we're gonna talk about tests with flutter and the importance of doing that in your projects, so let's get started.

## Why do we need to test?

As our flutter project grows the importance of testing it grows too, so since your code was well made using good patterns and following the best practices, it makes easier to implement a test to grant you the app works as you've projected for. 

## Concept

There are some concepts of how to write tests and provide better and well-projected apps, one of the most common is the TDD (Test Driven Development), which says that you have to write your test first and then write the code, and repeat the process of how many times you need to ensure that everything is passing in test and the code only does what it has to do.

## Types of test

With Flutter we have three types of tests, they are:

- Unit test: as the name said, this kind of test has the purpose of checking if the minimum part of your code is working as expected, this includes for example functions and classes.

- Widget test: This one verifies if the widgets are working as expected, so the purpose of this kind of test is to check the UI consistency.

- Integration test: The last one, the integration test, it's personally my favorite type, this is like a mix of the two types of the test described before, with this you can check an entire flow, so you can verify if the checkout of an e-commerce app is working as expected for example.

With all that said let's dive inside some examples to understand a little bit better. In this case, I will show how to test a login because it is a common feature for most of the projects.

To get started, suppose that you've already the flutter in your O.S then just create a project with the command `flutter create login_test` where the name `login_test` can be changed if you wanna give another name for your project, with this created verify in your root directory and you be able to find a folder called `test`, all of your testing code should be inside that and by default, all the classes created for this purpose have to be in the the `_test.dart` to let the language recognize this as a test class.

To get started using the TDD concept, let's create the unit test first before the implementation.

## Unit test

delete the file that comes with the project and create a file called `unit_login_test.dart` in `test` folder with the following code:

``` dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('Login controller', () {
    test("success login", () {
      LoginController controller = LoginController();
      expect(controller.login("username", "password"), true);
    });

    test("failed login", () {
      LoginController controller = LoginController();
      expect(controller.login("invalidUsername", "invalidPassword"), false);
    });
  });
}

```

We create a main function, which starts in this case these tests, we have to import the class `flutter_test` to execute as we want, to do this we create a `group` and inside put all our test cases, initiate our class and execute the function with some given parameters, and the expected return in expect test function to do that we imagine that we have a controller and inside a login function that receives the username and the password and if match returns a boolean. To execute now we have to create a file called `login_controller.dart` inside the `lib` folder with the following code: 

``` dart
class LoginController {
  bool login(String username, String password) {
    return username == "username" && password == "password";
  }
}

```

With that, we only have to import this class inside our test class and execute it to see the result 

`import 'package:login_test/login_controller.dart';`


![Unit test passing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gizzgl646gg2hhslbsjl.png)

Now let's move to the second type of test

## Widget test

Like the unit test, we will create the test file and implement that, to get started create a file called `widget_login_test.dart` and put the following code:

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

void main() {
  testWidgets("Login UI", (tester) async {
    await tester.pumpWidget(const MaterialApp(home: LoginPage()));

    final username = find.byKey(const Key("usernameTextField"));
    final password = find.byKey(const Key("passwordTextField"));
    final loginButton = find.byKey(const Key("loginButton"));

    expect(username, findsOneWidget);
    expect(password, findsOneWidget);
    expect(loginButton, findsOneWidget);

    await tester.enterText(username, "username");
    await tester.enterText(password, "password");
    await tester.tap(loginButton);

    await tester.pump();
    final successMessage = find.byKey(const Key('success'));
    expect(successMessage, findsOneWidget);
  });
}

```

The widget test renders the widgets and you can perform actions like `tap()` and `enterText()` to interact with the components, in this test case we need to search using the Key parameter, you can do it using a text, but the Key gives you more confidence about getting the right thing. Now to implement the code just create a file called `login_page.dart` and put this code below:

``` dart
import 'package:flutter/material.dart';
import 'package:login_test/login_controller.dart';

LoginController loginController = LoginController();

TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          TextField(
            controller: usernameController,
            key: const Key('usernameTextField'),
          ),
          TextField(
            controller: passwordController,
            key: const Key('passwordTextField'),
          ),
          const SizedBox(height: 16),
          ElevatedButton(
            key: const Key('loginButton'),
            child: const Text("Login"),
            onPressed: () {
              // Example login logic
              if (loginController.login(
                  usernameController.text, passwordController.text)) {
                showDialog(
                  context: context,
                  builder: (_) => const AlertDialog(
                    key: Key('success'),
                    title: Text("Login Successful"),
                  ),
                );
              }
            },
          ),
        ],
      ),
    );
  }
}

```

Now we only need to import the `LoginPage` in our widget test file, just type `import 'package:login_test/login_page.dart';` with that you can execute your test and should see something like 


![Widget test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yax9zfxi0saeu2t2yzqf.png)

## Integration test

This is the last type of test, it works by executing a complete feature, to do that you need a real device, to start we need to add a new dependency in our file `pubspec.yaml` in the section `dev_dependencies` called `integration_test: sdk: flutter`, after this just type `flutter pub get` to update the dependencies, with this set up complete you need to create a folder called `integration_test` in the root directory, then insert a file who you can call `app_test.dart`with the code below:

``` dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:login_test/login_page.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  WidgetsFlutterBinding.ensureInitialized();
  group('LoginPage', () {
    testWidgets("Login UI", (tester) async {
      await tester.pumpWidget(const MaterialApp(home: LoginPage()));

      final username = find.byKey(const Key("usernameTextField"));
      final password = find.byKey(const Key("passwordTextField"));
      final loginButton = find.byKey(const Key("loginButton"));

      expect(username, findsOneWidget);
      expect(password, findsOneWidget);
      expect(loginButton, findsOneWidget);

      await tester.enterText(username, "username");
      await tester.pumpAndSettle(const Duration(seconds: 1));
      await tester.enterText(password, "password");
      await tester.pumpAndSettle(const Duration(seconds: 1));
      await tester.tap(loginButton);

      await tester.pumpAndSettle(const Duration(seconds: 1));
      final successMessage = find.byKey(const Key('success'));
      expect(successMessage, findsOneWidget);
    });
  });
}

```

this code is very similar to the widget test, but we have a few differences, we use `IntegrationTestWidgetsFlutterBinding.ensureInitialized();` and `WidgetsFlutterBinding.ensureInitialized();` to ensure that we have the integration test and the widgets ready to start the process.

## Bonus

With an additional part of this article, we gonna see how to get a visual report of the test coverage, if you don't wanna learn this just skip to the conclusion.

to do that we have to run in terminal `flutter test --coverage`, after executing this should create a folder called coverage with a file named lcov.info inside as we see below:


![Coverage folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f97uoy7j1gm1romn06c5.png)

after that you have to convert this file into a bunch of files to be read by a web server, to continue just type in your terminal `genhtml coverage/lcov.info -o coverage/html` where the `coverage/lcov.info` is the path of the file created above and the `coverage/html` after the flag -o is the directory where I want to save the generated files when this part finish, you will see a folder with the given name created where you choose.

to finish we have to expose this directory to access in a web browser, to do that I recommend getting the `dhttpd` library from [here](https://pub.dev/packages/dhttpd) just type in your terminal `dart pub global activate dhttpd` with this you've downloaded the library globally, after this just type `dhttpd --path coverage/html/` and open a browser to put the URL `http://localhost:8080` and finally you will be able to see a full report about your test like you see bellow:


![Flutter coverage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1iisor30wrk3zzoxgfa.png)

With this we finish the bonus part, this could be essential to understand where the code is not covered and where the tests are failing.

## Conclusion

As we see, the tests give us more confidence in the final result focus on the release, this process can take more time to finish a feature, but the gain with that is more valuable than if a bug was reported after distribute of the project, so hope everybody understands how to do and the importance, hope to see you next time.

If you wanna see the full code, i've uploaded the code, the link is: [Repository](https://github.com/Matteuus/login_test)