选择另一个项目时,Flutter 小部件测试不会触发 DropdownButton.onChanged

Flutter widget test does not trigger DropdownButton.onChanged when selecting another item

我正在编写一个 Flutter 网络应用程序,并将一些小部件测试添加到我的代码库中。我很难让 flutter_test 按预期工作。我面临的当前问题是尝试 select DropdownButton 中的值。

下面是重现问题的完整小部件测试代码:

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'));
    });
  });
}

此测试未达到最终预期 -- expect(widget.value, equals('Greater'));

永远不会调用 onChanged 回调,正如我在调试器中看到的那样,或者在输出中查找我的打印语句。

测试 DropdownButton 行为的魔法是什么?

测试时,在任何用户交互相关代码之后添加 tester.pump() 调用很重要。

下拉按钮的测试与通常的小部件略有不同。对于所有情况,最好参考 here,这是对 dropdownbutton 的实际测试。

测试时的一些提示。

  1. DropdownButton 由按钮的 'IndexedStack' 和菜单项列表的普通堆栈组成。
  2. 您为 DropDownMenuItem 分配的密钥和文本以某种方式提供给上述堆栈中的两个小部件。
  3. 点击时选择返回的小部件列表中的最后一个元素。
  4. 此外,下拉按钮需要一些时间来制作动画,因此我们按照来自 flutter 的参考测试中的建议调用 tester.pump 两次。
  5. DropdownButtonvalue属性不会自动改变。它必须使用 setState 进行设置。因此,您的最后一行断言是错误的,除非您将测试包装在 here.
  6. 之类的 StatefulBuilder 中,否则它将不起作用

有关如何使用 DropDownButton 的更多详细信息,请查看此 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'));
      
    });
  });
}