0

I am following this tutorial https://docs.flutter.dev/development/ui/interactive#the-parent-widget-manages-the-widgets-state and I have the following in main.dart:

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    Widget statesSection = Container(
        padding: const EdgeInsets.all(32),
        child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [StatefulParentWidget()]));
    return MaterialApp(
        title: 'Flutter Layout',
        home: Scaffold(
            appBar: AppBar(title: const Text("Flutter Layout")),
            body: ListView(children: [
              statesSection
            ])));
  }

It doesn't find anything at all in the following test code:

testWidgets('State management tests', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(const MyApp());
    expect(find.byType(StatefulParentWidget), findsOneWidget); // Fails!
    expect(find.text("Inactive"), findsOneWidget); // Fails!
    expect(find.text("Active"), findsNothing); // Fails!
});

Test error message:

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: exactly one matching node in the widget tree
  Actual: _WidgetTypeFinder:<zero widgets with type "StatefulParentWidget" (ignoring offstage
widgets)>
   Which: means none were found but one was expected

Any advice and insight is appreciated. https://github.com/khteh/flutter

2
  • 1
    A minimal project to reproduce this is needed. Commented Jul 2, 2022 at 6:49
  • what you really need is to use skipOffstage parameter: for example: expect(find.byType(StatefulParentWidget, skipOffstage: false), findsOneWidget); Commented Jul 2, 2022 at 10:05

1 Answer 1

1

Finale

Change your code to:

              // titleSection,
              // buttonsSection,
              // textSection,
              statesSection

and the test will pass for

  testWidgets('State management tests', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(const MyApp());
    expect(find.byType(Scaffold), findsOneWidget);
    expect(find.byType(StatefulParentWidget), findsOneWidget);

    expect(find.text("Inactive"), findsOneWidget);
    expect(find.text("Active"), findsNothing);
    //StatefulParentWidget statefulParentWidget = const StatefulParentWidget();
    //tester.state(find.byWidget(statefulParentWidget));
    //expect(find.byWidget(tapboxB), findsOneWidget);
  });

So the test only can find widgets rendered already, and in your case, the widgets in statesSection were once off the stage.

Former Discussion

1 With Scaffod

If you were facing the same exception message as follows:

flutter: (The following exception is now available via WidgetTester.takeException:)
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building MainPage:
flutter: No MediaQuery widget ancestor found.
flutter: Scaffold widgets require a MediaQuery widget ancestor.

Change your test code to this, it should work

void main() {
  testWidgets('State management tests', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(MaterialApp(
      home: const MainPage(),
    ));
    expect(find.byType(ParentWidget), findsOneWidget); // Fails!
    expect(find.text("Inactive"), findsOneWidget); // Fails!
    expect(find.text("Active"), findsNothing); // Fails!
  });
}

Full code is here:

void main() {
  testWidgets('State management tests', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(MaterialApp(home: const MainPage(),));
    expect(find.byType(ParentWidget), findsOneWidget); // Fails!
    expect(find.text("Inactive"), findsOneWidget); // Fails!
    expect(find.text("Active"), findsNothing); // Fails!
  });
}

class MainPage extends StatelessWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Hello"),
        ),
        body: Container(
            padding: const EdgeInsets.all(32),
            child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: const [ParentWidget()])));
  }
}

// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  const TapboxB({
    super.key,
    this.active = false,
    required this.onChanged,
  });

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}

2 Without Scaffod

If you want to test the widget solely, then you have to replace your Text widget with this:

          child: Text(
            active ? 'Active' : 'Inactive',
            textDirection: TextDirection.ltr,
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),

In my first example, the Scallfold does the trick so you can test without telling textDirection attribution. Please refer to TextDirection enum for further reading.

Full code is here:

void main() {
  testWidgets('State management tests', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(statesSection);
    expect(find.byType(ParentWidget), findsOneWidget); // Fails!
    expect(find.text("Inactive"), findsOneWidget); // Fails!
    expect(find.text("Active"), findsNothing); // Fails!
  });
}

Widget statesSection = Container(
    padding: const EdgeInsets.all(32),
    child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: const [ParentWidget()]));

class MainPage extends StatelessWidget {
  const MainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Hello", textDirection: TextDirection.ltr),
        ),
        body: Container(
            padding: const EdgeInsets.all(32),
            child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: const [ParentWidget()])));
  }
}

// ParentWidget manages the state for TapboxB.

//------------------------ ParentWidget --------------------------------

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active = false;

  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxB(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

//------------------------- TapboxB ----------------------------------

class TapboxB extends StatelessWidget {
  const TapboxB({
    super.key,
    this.active = false,
    required this.onChanged,
  });

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            active ? 'Active' : 'Inactive',
            textDirection: TextDirection.ltr,
            style: const TextStyle(fontSize: 32.0, color: Colors.white),
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active ? Colors.lightGreen[700] : Colors.grey[600],
        ),
      ),
    );
  }
}
Sign up to request clarification or add additional context in comments.

11 Comments

Scaffold is there. It is a boilerplate code generated by the Flutter extension in VSCode. The test error message is very straight-forward mentioning what it doesn't find. Not the one you mentioned. I have included it in my post.
@KokHowTeh I have pasted both code with & without Scaffold here and all of them passed the tests. You may compare your code with mine and figure out what happened. From your error message, maybe your widget the test is trying to find is in offstage state so it cannot be found. Just a possibility.
Why ` await tester.pumpWidget(MaterialApp(home: const MainPage(),));` is needed? Why can't I use await tester.pumpWidget(const MyApp());?
And why do you have to copy-paste the code in main.dart into the test Widget statesSection = Container(...?
what you really need is to use skipOffstage parameter: for example: expect(find.byType(StatefulParentWidget, skipOffstage: false), findsOneWidget);
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.