flutterwidget-test-flutter

Widget testing DropdownButton finds duplicate DropdownMenuItems


I'm trying to write widget tests for a DropdownButton in my app. I noticed that after tapping the button to open it the call to find.byType(DropdownMenuItem) is returning double the expected number of DropdownMenuItems.

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

enum MyColor {
  blue,
  green,
  red,
  yellow,
  black,
  pink
}

Future<void> main() async {
//   runApp(MyApp());
  
  // tests
  group('dropdown tests', () {
    testWidgets('how many elements should be found?', (tester) async {
      await tester.pumpWidget(MyApp());
      await tester.pumpAndSettle();
      
      expect(find.byType(DropdownButton<MyColor>), findsOneWidget);
      await tester.tap(find.byType(DropdownButton<MyColor>));
      await tester.pumpAndSettle();
      
      // fails
      // expect(find.byType(DropdownMenuItem<MyColor>), findsNWidgets(MyColor.values.length));
      
      // passes
      expect(find.byType(DropdownMenuItem<MyColor>), findsNWidgets(MyColor.values.length * 2));
    });
  });
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  MyColor selected = MyColor.blue;
  
  @override
  Widget build(BuildContext context) {
    return DropdownButton<MyColor>(
      value: selected,
      items: MyColor.values.map((col) {
        return DropdownMenuItem<MyColor>(
          child: Text(col.name),
          value: col,
        );
      }).toList(),
      onChanged: (value) {
        if (value == null) {
          return;
        }
        
        print('${value.name} selected');
        setState(() {
          selected = value;
        });
      }
    );
  } 
}

Dartpad: https://dartpad.dev/?id=ce3eadff6bd98e6005817c70883451a0

I suspect that this has something to do with how Flutter renders the scene. I looked into the widget tests for the dropdown in the Flutter repo but I don't see any difference between my setup and theirs, but I also don't see any calls to find.byType(DropdownMenuItem). Does anyone know why this happens? Or is there an error in my code?


Solution

  • When an DropdownButton is rendered initially all items are rendered with IndexedStack and based on the selected value we see one visible item at the top

    Once you tap on DropdownButton a _DropdownRoute route is pushed with all the items

    // Each item appears twice, once in the menu and once // in the dropdown button's IndexedStack.

    https://github.com/flutter/flutter/blob/504e66920005937b6ffbc3ccd6b59d594b0e98c4/packages/flutter/test/material/dropdown_test.dart#L2230

    Once you tap on of the DropdownMenuItem items the number of found widgets will go back to 6