Testing the widget’s appearance in Flutter
Tags: flutter, test, dart, ci
Published: 2023-03-27 09:15:34

A good programmer writes tests to find errors and maintain the code. Tests are part of a programmer guy daily, but many programmers ignore an important kind of test, the **user interface** test. Can you think how hard to ensure the widget's appearance or don’t introduce a bug for a project with thousands of widgets? That’s much hard or maybe impossible to maintain this without tests. However, The Flutter **Golden Test** provides an easy way to help us ensure that widgets have the correct appearance.

In this article, you'll learn about improving your user interface testing experience with the [Alchemist](https://pub.dev/packages/alchemist) tool and any concepts of the **Golden Test**.

> The maintainable code is a testable code. - Unknown
> 

# What’s a Golden Test?

Remembering a little of any concepts of unit tests, the **Golden Test** provides a way to test the appearance of one single widget using a master image called **Golden File** which is considered the correct rendering of a widget.

# Setting up the Alchemist package

The tool configuration is pretty simple and easy. Let’s start installing the **Alchemist** package into the project with the command below:

```bash
flutter pub add alchemist --dev
```

Create a file to prepare some configurations before a test is executed called `flutter_test_config.dart` in the `test/` directory after installing the **Alchemist** tool successfully. In this file, we’ll prepare some configurations like render widget shadows and allow the display of raw text rather than obscure them.

```dart
import 'dart:async';

import 'package:alchemist/alchemist.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  return prepareGoldenTests(testMain);
}

void prepareGoldenTests(FutureOr<void> Function() testMain) {
  return AlchemistConfig.runWithConfig(
    config: const AlchemistConfig(
      platformGoldensConfig: PlatformGoldensConfig(
        renderShadows: true,
        obscureText: false,
      ),
    ),
    run: testMain,
  );
}
```

# Writing a Golden Test

Let’s write one widget to test before we start to write the **Golden Test**. We created a widget for this section displaying an NBA game schedule.

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

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

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(
        vertical: 15,
        horizontal: 20,
      ),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: const [
                      Text(
                        'Boston Celtics',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.w400,
                        ),
                      ),
                      SizedBox(height: 15),
                      Text(
                        'Chicago Bulls',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.w400,
                        ),
                      ),
                    ],
                  ),
                ),
                const Expanded(
                  child: Center(
                    child: Text(
                      '08:00 PM',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
```

## Create a testGoldens test

With a widget to test the appearance, becomes easy to write a test. Use the `goldenTest()` function from the `alchemist` package to define one test, this function allows you to define a widget to test the appearance and the file name to generate the **Golden File**.

```dart
goldenTest(
  'displayed the basketball game',
  fileName: 'baskteball_game_widget',
  constraints: const BoxConstraints(
      maxWidth: 300,
  ),
  builder: () => const GameItem(),
);
```

## Generating the Golden File

It's time to generate the **Golden File** after writing the test using the `goldenTest()` function. You can execute the command below to generate the **Golden File**.

```bash
flutter test --update-goldens
```

You may notice that the `test` directory has been updated with a new directory called `goldens` containing the **Golden File**s generated by the **Golden Test**, for example:

```bash
test/
	goldens/
		ci/
			- baskteball_game_widget.png
		macos/
			- baskteball_game_widget.png
```

The golden tests generate the `macos/` subdirectory because while I'm writing this article I'm using a **MacOS** operation system. Whether you are using **Linux** or **Windows** your golden tests will generate the subdirectory dealing with your operating system with subtle differences between the platforms. 

# Golden Test in continuous integration workflow

You may notice that the `goldens/` directory also has the `ci/` subdirectory. The `ci/` subdirectory is generated to deal with the subtle differences in rendering between **MacOS**, **Linux**, and **Windows** platforms.

## Enabling the continuous integration using —dart-define

To enable continuous integration environment is necessary to disable the tests locally. We can use the `IS_CONTINUOUS_INTEGRATION` environment declaration to identify when the tests are running on the continuous integration environment and obtain this value using the `bool.fromEnvironment`.

Go back to the `flutter_test_config.dart` in the `test/` directory and update the `prepareGoldenTests` to obtain the `IS_CONTINUOUS_INTEGRATION` declaration and disables the tests locally when this value is **true**.

```dart
void prepareGoldenTests(FutureOr<void> Function() testMain) {
  const isContinuosIntegration = bool.fromEnvironment('IS_CONTINUOUS_INTEGRATION');

  return AlchemistConfig.runWithConfig(
    config: const AlchemistConfig(
      platformGoldensConfig: PlatformGoldensConfig(
        renderShadows: true,
        obscureText: false,
        enabled: isContinuosIntegration
      ),
    ),
    run: testMain,
  );
}
```

## Running tests using the **—dart-define**

Running the tests using the `—-dart-define` environment declaration is pretty simple. See the command below to run the tests for a continuous integration environment. 

```bash
flutter test --dart-define=IS_CONTINUOUS_INTEGRATION=true
```

# Conclusion

The **Golden Test** is important for maintaining the code and the project's health. Sometimes, the standard **Golden Test** tool is insufficient to cover all cases possible; therefore, using the [Alchemist](https://pub.dev/packages/alchemist) tool gives developers a powerful tool to develop the appearance test and a way to use it in their continuous integration environments.