7

I am writing a Flutter web app, and adding some widget tests to my codebase. I am having difficulty making flutter_test work as intended. The current problem I face is trying to select a value in a DropdownButton.

Below is the complete widget test code that reproduces the problem:

void main() {
  group('description', () {
    testWidgets('description', (WidgetTester tester) async {
      await tester.pumpWidget(MaterialApp(
        home: Card(
          child: Column(
            children: [
              Expanded(
                child: DropdownButton(
                  key: Key('LEVEL'),
                  items: [
                    DropdownMenuItem<String>(
                      key: Key('Greater'),
                      value: 'Greater',
                      child: Text('Greater'),
                    ),
                    DropdownMenuItem<String>(
                      key: Key('Lesser'),
                      value: 'Lesser',
                      child: Text('Lesser'),
                    ),
                  ],
                  onChanged: (value) {
                    print('$value');
                  },
                  value: 'Lesser',
                ),
              )
            ],
          ),
        ),
      ));

      expect((tester.widget(find.byKey(Key('LEVEL'))) as DropdownButton).value,
          equals('Lesser'));

      await tester.tap(find.byKey(Key('LEVEL')));

      await tester.tap(find.byKey(Key('Greater')));
      await tester.pumpAndSettle();

      expect((tester.widget(find.byKey(Key('LEVEL'))) as DropdownButton).value,
          equals('Greater'));
    });
  });
}

This test fails on the final expectation -- expect(widget.value, equals('Greater'));

The onChanged callback is never invoked, as I can see in the debugger, or looking for my print statement in the output.

What is the magic to test the behavior of a DropdownButton?

beer geek
  • 371
  • 2
  • 12

1 Answers1

9

While testing it is important to add a tester.pump() call after any user interaction related code.

The testing of dropdown button is slightly different from usual widgets. For all the cases it is better to refer here which is the actual test for dropdownbutton.

Some hints while testing.

  1. DropdownButton is composed of an 'IndexedStack' for the button and a normal stack for the list of menu items.
  2. Somehow the key and the text you assign for DropDownMenuItem is given to both the widgets in the above mentioned stacks.
  3. While tapping choose the last element in the returned widgets list.
  4. Also the dropdown button takes some time to animate so we call tester.pump twice as suggested in the referred tests from flutter.
  5. The value property of the DropdownButton is not changed automatically. It has to set using setState. So your last line of assertion is wrong and will not work unless you wrap your test in a StatefulBuilder like here.

For more details on how to use DropDownButton check this post

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

void main() {
  group('description', () {
    testWidgets('description', (WidgetTester tester) async {
      String changedValue = 'Lesser';
      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: Center(
            child: RepaintBoundary(
              child: Card(
                child: Column(
                  children: [
                    Expanded(
                      child: DropdownButton(
                        key: Key('LEVEL'),
                        items: [
                          DropdownMenuItem<String>(
                            key: ValueKey<String>('Greater'),
                            value: 'Greater',
                            child: Text('Greater'),
                          ),
                          DropdownMenuItem<String>(
                            key: Key('Lesser'),
                            value: 'Lesser',
                            child: Text('Lesser'),
                          ),
                        ],
                        onChanged: (value) {
                          print('$value');
                          changedValue = value;
                        },
                        value: 'Lesser',
                      ),
                    )
                  ],
                ),
              ),
            ),
          ),
        ),
      ));

      expect((tester.widget(find.byKey(Key('LEVEL'))) as DropdownButton).value,
          equals('Lesser'));
      // Here before the menu is open we have one widget with text 'Lesser'
      await tester.tap(find.text('Lesser'));
      // Calling pump twice once comple the the action and
      // again to finish the animation of closing the menu.
      await tester.pump();
      await tester.pump(Duration(seconds: 1));

      // after opening the menu we have two widgets with text 'Greater'
      // one in index stack of the dropdown button and one in the menu .
      // apparently the last one is from the menu.
      await tester.tap(find.text('Greater').last);
      await tester.pump();
      await tester.pump(Duration(seconds: 1));

      /// We directly verify the value updated in the onchaged function.
      expect(changedValue, 'Greater');

      /// The follwing expectation is wrong because you haven't updated the value
      ///  of dropdown button.
      // expect((tester.widget(find.byKey(Key('LEVEL'))) as DropdownButton).value,
      //     equals('Greater'));
      
    });
  });
}

Abhilash Chandran
  • 6,803
  • 3
  • 32
  • 50
  • 2
    I edited the code above to show that I've added the call to tester.pump(), but still the test fails because the DropdownButton value is still 'Lesser'. – beer geek Oct 23 '20 at 12:15
  • Which version of flutter are you using.? could you also post flutter doctor summary so that will try to reproduce. In my case its failing because somehow `finder` finds more than one widget with the same key . – Abhilash Chandran Oct 23 '20 at 13:01