DEV Community

Yelaman Yelmuratov
Yelaman Yelmuratov

Posted on

Approval Testing with Widget tests | Flutter / Dart 🎯

Hi there, I already wrote an article about ApprovalTests for unit tests.
In this article we will touch on Widget tests and how this package can be useful for us.

Let's briefly go through again what exactly are ApprovalTests?

In manual code testing, developers usually write automated tests to guard against regressions, specifying the expected outcomes directly in the code. ApprovalTests uses a similar method but stores the expected outcomes in a file instead. This file is generated by the test library rather than the developer, making the process of writing and maintaining tests more efficient. As a result, developers can spend more time on the functional code rather than the test code.

πŸ“‹ How it works step by step

  • The first run of the test automatically creates an approved file if there is no such file.
  • If the changed results match the approved file perfectly, the test passes.
  • If there's a difference, a reporter tool will highlight the mismatch and the test fails.
  • If the test is passed, the received file is deleted automatically. You can change this by changing the deleteReceivedFile value in options. If the test fails, the received file remains for analysis.

Instead of writing:

    testWidgets('home page', (WidgetTester tester) async {
        await tester.pumpWidget(const MyApp());
        await tester.pumpAndSettle();

        expect(find.text('You have pushed the button this many times:'), findsOneWidget);
        expect(find.text('0'), findsOneWidget);
        expect(find.byWidgetPredicate(
            (Widget widget) => widget is Text && widget.data == 'hello' && 
            widget.key == ValueKey('myKey'),
        ), findsOneWidget);
        expect(find.text('Approved Example'), findsOneWidget);
    });
Enter fullscreen mode Exit fullscreen mode

Write this:

    testWidgets('home page', (WidgetTester tester) async {
        await tester.pumpWidget(const MyApp());
        await tester.pumpAndSettle();

        await tester.approvalTest();
    });
Enter fullscreen mode Exit fullscreen mode

To include your project's custom widget types in your test, and to perform post-test checks, add calls to Approved.setUpAll() to your tests' setUpAll calls, like so:

    main() {
        setUpAll(() {
            Approved.setUpAll();
        });
    }
Enter fullscreen mode Exit fullscreen mode

And the result will be something like this:

Flutter example result

Console log result

But if we for example change something and run the text, it will show us the difference, where it differs. There are several types of Reporter in the project, but the standard is CommandLineReporter, which gives the difference on the command line. Other reporters can be found in the project's Github repository.

CommandLineReporter example

Approving Results

Approving results just means saving the .approved.txt file with your desired results.

We’ll provide more explanation in due course, but, briefly, here are the most common approaches to do this.

β€’ Via Diff Tool

Most diff tools have the ability to move text from left to right, and save the result.
How to use diff tools is just below, there is a Comparator class for that.

β€’ Via CLI command

You can run the command in a terminal to review your files:

dart run approval_tests:review
Enter fullscreen mode Exit fullscreen mode

After running the command, the files will be analyzed and you will be asked to choose one of the options:

  • y - Approve the received file.
  • n - Reject the received file.
  • view - View the differences between the received and approved files. After selecting v you will be asked which IDE you want to use to view the differences.

The command dart run approval_tests:review has additional options, including listing files, selecting
files to review from this list by index, and more. For its current capabilities, run

dart run approval_tests:review --help
Enter fullscreen mode Exit fullscreen mode

β€’ Via approveResult property

If you want the result to be automatically saved after running the test, you need to use the approveResult property in Options:

void main() {
  test('test JSON object', () {
    final complexObject = {
      'name': 'JsonTest',
      'features': ['Testing', 'JSON'],
      'version': 0.1,
    };

    Approvals.verifyAsJson(
      complexObject,
      options: const Options(
        approveResult: true,
      ),
    );
  });
}
Enter fullscreen mode Exit fullscreen mode

this will result in the following file
example_test.test_JSON_object.approved.txt

{
  "name": "JsonTest",
  "features": [
    "Testing",
    "JSON"
  ],
  "version": 0.1
}
Enter fullscreen mode Exit fullscreen mode

β€’ Via file rename

You can just rename the .received file to .approved.

❓ Which File Artifacts to Exclude from Source Control

You must add any approved files to your source control system. But received files can change with any run and should be ignored. For Git, add this to your .gitignore:

*.received.*
Enter fullscreen mode Exit fullscreen mode

Show some πŸ’™ and star the repo to support the project! 🫰

For any questions, feel free to reach out via Telegram or email at yelaman.yelmuratov@gmail.com.

Top comments (0)