Widget testing DropdownButton finds duplicate DropdownMenuItems

Issue

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

  • At that stage find.byType(DropdownMenuItem<MyColor>) will find 6
    items

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

  • At that stage find.byType(DropdownMenuItem<MyColor>) will find 12
    items (the first 6 items are from IndexedStack and the second 6
    items are from the new route)
    So the number of items should be double at this stage as documented in the flutter tests as well

// 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

Answered By – Raouf Rahiche

Answer Checked By – Marilyn (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *