简体   繁体   中英

How do I find a widget in widget tree using finder?

So I'm trying to set up integration testing for my app. Now I need to know how widget testing works in order to do this properly. When trying to write a simple widget test it is failing giving the following error.

TestFailure (Expected: exactly one matching node in the widget tree
  Actual: _DescendantFinder:<zero widgets with the given widget (FloatingActionButton(hero)) that has ancestor(s) with the given widget (SizedBox) (ignoring offstage widgets)>
   Which: means none were found but one was expected
)

Below are the different relevant files in reference to this code

App_test.dart This is the test file code I wrote

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('end-to-end test', () {
    testWidgets('Just a small test', (WidgetTester tester) async {
      // app.main();
      KeyboardVisibilityTesting.setVisibilityForTesting(true);
      await tester.pumpWidget(MyNovatium());
      await tester.pumpAndSettle(const Duration(seconds: 2));
      expect(find.descendant(of: find.byWidget(const SizedBox()), matching: find.byWidget(FloatingActionButton(onPressed: (){}, heroTag: 'btnLogin',))), findsOneWidget);
    });
  });
}

MyNovatium.dart This is the code for my Novatium file which is being called in main.dart

class MyNovatium extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return OverlaySupport.global(
      child: OKToast(
        child: VMaterialApp<Object>(
          title: kProductName,
          debugShowCheckedModeBanner: false,
          localizationsDelegates: context.localizationDelegates..add(FormBuilderLocalizations.delegate),
          supportedLocales: context.supportedLocales,
          locale: context.locale,
          child: MultiBlocProvider(
            providers: <BlocProvider<dynamic>>[
              BlocProvider<AuthenticationBloc>(
                lazy: false,
                create: (BuildContext context) => AuthenticationBloc()..add(AuthenticationStarted()),
              ),
              BlocProvider<SettingsBloc>(
                lazy: false,
                create: (BuildContext context) => SettingsBloc(authenticationBloc: context.read<AuthenticationBloc>()),
              ),
              BlocProvider<BusinessBloc>(
                lazy: false,
                create: (BuildContext context) => BusinessBloc(authenticationBloc: context.read<AuthenticationBloc>()),
              ),
              BlocProvider<ClientBloc>(
                lazy: false,
                create: (BuildContext context) => ClientBloc(authenticationBloc: context.read<AuthenticationBloc>()),
              ),
              BlocProvider<CustomersBloc>(
                lazy: false,
                create: (BuildContext context) => CustomersBloc(authenticationBloc: context.read<AuthenticationBloc>()),
              ),
            ],
            child: MainRouter(),
          ),
        ),
      ),
    );
  }
}

Main_router.dart Below is code for main router file. I'm adding it just in case but there is basically no need to read this part. The initial page we see is the login view is below this code block.

class MainRouter extends StatefulWidget {
  @override
  State<MainRouter> createState() => _MainRouterState();
}

class _MainRouterState extends State<MainRouter> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        final GlobalKey<NavigatorState> _navigatorGlobalKey = getValueForScreenType<GlobalKey<NavigatorState>>(context: context, mobile: _mobGlobKey, tablet: _tabGlobKey, desktop: _desGlobKey);

        return VRouter(
          navigatorKey: _navigatorGlobalKey,
          theme: appThemeData[CETheme.light],
          buildTransition: (Animation<double> animation1, _, Widget child) {
            return FadeTransition(opacity: animation1, child: child);
          },
          transitionDuration: const Duration(milliseconds: 150),
          builder: (BuildContext context, Widget child) {
            return MultiBlocListener(
              listeners: <BlocListener<dynamic, dynamic>>[],
              child: child,
            );
          },
          routes: <VRouteElement>[
            splashRouter(context),
            loginRouter(context),
            signupRouter(context),
            VGuard(
              beforeEnter: (VRedirector vRedirector) async => (context.read<AuthenticationBloc>().state is AuthenticationLoadSuccess) ? null : vRedirector.to('/login'),
              stackedRoutes: <VRouteElement>[
                settingsRouter(context),
              ],
            ),

            VRouteRedirector(path: ':_(.+)', redirectTo: '/'),
          ],
        );
      },
    );
  }
}

Login_view.dart Below is the code for the login_view which is the entry screen we see after the splash screen. It is pretty big so I'm just adding the build parameter of it and will try to cut down unnecessary code.

  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: KBMOverlay(
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              BlocBuilder<LoginBloc, LoginState>(
                builder: (BuildContext context, LoginState state) {
                  return Expanded(
                    child: Center(
                      child: MultiBlocListener(
                        listeners: <BlocListener<dynamic, dynamic>>[
                          child: SingleChildScrollView(
                          controller: _loginScrollController,
                          child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
                            builder: (BuildContext context, AuthenticationState state) {
                              if (state is AuthenticationLoadInProgress) {
                                return const KBMLoading();
                              }
                              return Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  KBMCard(
                                    child: Padding(
                                      padding: const EdgeInsets.all(kDefaultPadding),
                                      child: ConstrainedBox(
                                        constraints: const BoxConstraints(maxWidth: 300),
                                        child: Column(
                                          mainAxisSize: MainAxisSize.min,
                                          children: <Widget>[
                                            Padding(
                                              padding: const EdgeInsets.only(bottom: kDefaultPadding / 2),
                                              child: _kbt.h1(kProductName),
                                            ),
                                            Padding(
                                              padding: const EdgeInsets.only(bottom: kDefaultPadding * 2),
                                              child: _kbt.h4('general.justTest'),
                                            ),
                                            Form(
                                              key: _loginFormKey,
                                              child: AutofillGroup(
                                                child: Column(
                                                  children: <Widget>[
                                                    Padding(
                                                      padding: const EdgeInsets.only(bottom: kDefaultPadding),
                                                      child: TextField(
                                                        key: _usernameKey,
                                                        autofillHints: const <String>[AutofillHints.username],
                                                        keyboardType: TextInputType.emailAddress,
                                                        decoration: InputDecoration(
                                                          suffixIcon: const Icon(Icons.person_outline),
                                                          hintStyle: _kbt.sb2().copyWith(color: Colors.grey.withOpacity(0.5)),
                                                          contentPadding: const EdgeInsets.symmetric(horizontal: 5.0),
                                                          labelText: 'E-Mail',
                                                          disabledBorder: OutlineInputBorder(
                                                            borderRadius: BorderRadius.zero,
                                                            borderSide: BorderSide(color: Theme.of(context).colorScheme.primary.withOpacity(0), width: 0),
                                                          ),
                                                          focusedBorder: OutlineInputBorder(
                                                            borderRadius: BorderRadius.zero,
                                                            borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
                                                          ),
                                                          enabledBorder: OutlineInputBorder(
                                                            borderRadius: BorderRadius.zero,
                                                            borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
                                                          ),
                                                        ),
                                                        controller: _usernameController,
                                                        autocorrect: false,
                                                        onSubmitted: (String value) {
                                                          if (state is! LoginLoadInProgress || state is! LoginLoadSuccess) {
                                                            _onLoginButtonPressed();
                                                          }
                                                        },
                                                      ),
                                                    ),
                                                    TextField(
                                                      key: _passwordKey,
                                                      autofillHints: const <String>[AutofillHints.password],
                                                      decoration: InputDecoration(
                                                        suffixIcon: const Icon(Icons.lock_outline),
                                                        hintStyle: _kbt.sb2().copyWith(color: Colors.grey.withOpacity(0.5)),
                                                        contentPadding: const EdgeInsets.symmetric(horizontal: 5.0),
                                                        labelText: 'general.password'.tr(),
                                                        disabledBorder: OutlineInputBorder(
                                                          borderRadius: BorderRadius.zero,
                                                          borderSide: BorderSide(color: Theme.of(context).colorScheme.primary.withOpacity(0), width: 0),
                                                        ),
                                                        focusedBorder: OutlineInputBorder(
                                                          borderRadius: BorderRadius.zero,
                                                          borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
                                                        ),
                                                        enabledBorder: OutlineInputBorder(
                                                          borderRadius: BorderRadius.zero,
                                                          borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
                                                        ),
                                                      ),
                                                      autocorrect: false,
                                                      controller: _passwordController,
                                                      obscureText: true,
                                                      onEditingComplete: () => TextInput.finishAutofillContext(),
                                                      onSubmitted: (String value) {
                                                        if (state is! LoginLoadInProgress || state is! LoginLoadSuccess) {
                                                          _onLoginButtonPressed();
                                                        }
                                                      },
                                                    ),
                                                    InkWell(
                                                      hoverColor: Colors.transparent,
                                                      splashColor: Colors.transparent,
                                                      highlightColor: Colors.transparent,
                                                      onTap: () {
                                                        setState(() {
                                                          _remember = !_remember;
                                                        });

                                                        if (_remember) _showRememberInfo();
                                                      },
                                                      child: Row(
                                                        mainAxisAlignment: MainAxisAlignment.center,
                                                        children: <Widget>[
                                                          Checkbox(
                                                            checkColor: Colors.white,
                                                            activeColor: Theme.of(context).colorScheme.primary,
                                                            value: _remember,
                                                            onChanged: (bool? remember) {
                                                              setState(() {
                                                                _remember = remember!;
                                                              });

                                                              if (_remember) _showRememberInfo();
                                                            },
                                                          ),
                                                          Flexible(
                                                            child: _kbt.h6('account.stayLoggedIn'),
                                                          ),
                                                        ],
                                                      ),
                                                    ),
                                                  ],
                                                ),
                                              ),
                                            ),
                                            if (state is LoginLoadInProgress || state is LoginLoadSuccess)
                                              const Padding(
                                                padding: EdgeInsets.only(bottom: kDefaultPadding, top: kDefaultPadding),
                                                child: SizedBox(height: 45, child: KBMLoading(size: 30)),
                                              )
                                            else
                                              Padding(
                                                padding: const EdgeInsets.only(bottom: kDefaultPadding, top: kDefaultPadding),
                                                child: SizedBox(
                                                  height: 45,
                                                  width: 300,
                                                  child: FloatingActionButton(
                                                    elevation: 1,
                                                    heroTag: 'btnLogin',
                                                    shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(3.0))),
                                                    onPressed: () {
                                                      if (state is! LoginLoadInProgress) _onLoginButtonPressed();
                                                    },
                                                    child: _kbt.button('login'),
                                                  ),
                                                ),
                                              ),
                                            Divider(
                                              thickness: 0.2,
                                              indent: 100,
                                              endIndent: 100,
                                              color: Theme.of(context).brightness == Brightness.light ? Theme.of(context).colorScheme.primary : Colors.grey,
                                            ),
                                            Padding(
                                              padding: const EdgeInsets.symmetric(vertical: kDefaultPadding),
                                              child: SizedBox(
                                                height: 45,
                                                width: 300,
                                                child: FloatingActionButton(
                                                  disabledElevation: 0,
                                                  elevation: 1,
                                                  heroTag: 'btnSignUp',
                                                  shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(3.0))),
                                                  backgroundColor: Colors.green,
                                                  hoverColor: Theme.of(context).hoverColor,
                                                  onPressed: () {
                                                    if (state is! LoginLoadInProgress || state is! LoginLoadSuccess) {
                                                      context.vRouter.to('/gurgeln');
                                                    }
                                                  },
                                                  child: _kbt.button('account.create'),
                                                ),
                                              ),
                                            ),
                                          ],
                                        ),
                                      ),
                                    ),
                                  ),
                                 ),
                                ],
                              );
                            },
                          ),
                        ),
                      ),
                    ),
                  );
                },
              )
            ],
          ),
        ),
      ),
    );
  }

Finally below is an ss of the login page. I'm trying to find the login button using finders in dart. The login button is a floatingactionbutton.

在此处输入图像描述

Check Out this example below created a simple example.

You can basically use Keys in order to find the decendant widget.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Test App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: const Key('homePageKey'),
      body: Column(
        children: const [SecondDescendant()],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      // You can add the key to the specific widget and if it finds any widget 
      // that means the widget is there in the tree and it has found it based on the key.
      key: const Key('secondDescendant'),
      child: const Text('Sample text'),
    );
  }
}

Below are the widget tests :

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

import 'package:download_file/main.dart';

void main() {
  testWidgets('render dencedent widget test', (WidgetTester tester) async {
    // Pump the widget
    await tester.pumpWidget(MyApp());

    expect(find.byKey(const Key('homePageKey')), findsOneWidget);
    expect(find.byKey(const Key('secondDescendant')), findsOneWidget);
  });
}

Let me know if it works or you are looking for something different.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM