0

I'm trying to run an integration test in my app. The screen is my login screen which leads to a signup flow and logged in to Home Screen. I'm using flutter integration test from the framework it self.

I've tried to run an integration test on the login screen but I get this error,

The following TestFailure object was thrown running a test: Expected: exactly one matching node in the widget tree Actual: _WidgetPredicateFinder:<zero widgets with widget matching predicate (Closure: (Widget) => bool) (ignoring offstage widgets)> Which: means none were found but one was expected

My Login screen looks like this

class LoginScreen extends StatefulWidget {
  static String tag = loginScreenRoute;

  const LoginScreen({Key? key}) : super(key: key);

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final _userLoginFormKey = GlobalKey<FormState>();

  String? _userName = "";
  String? _password = "";
  bool _invisiblePass = false;

  TextEditingController usernameController = TextEditingController();
  TextEditingController passwordController = TextEditingController();

  bool hasInterNetConnection = false;
  late StreamSubscription _connectionChangeStream;

  @override
  initState() {
    //Create instance
    ConnectionUtil connectionStatus = ConnectionUtil.getInstance();
    //Initialize
    connectionStatus.initialize();
    //Listen for connection change
    _connectionChangeStream =
        connectionStatus.connectionChange.listen(connectionChanged);

    super.initState();
  }

  @override
  void dispose() {
    _connectionChangeStream.cancel();
    super.dispose();
  }

  void connectionChanged(dynamic hasConnection) {
    setState(() {
      hasInterNetConnection = hasConnection;
      //print(isOffline);
    });
    if (!hasInterNetConnection) {
      offlineBar(context);
    }
  }

  final loading = Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: const <Widget>[
      CircularProgressIndicator(
        color: lightWTextColor,
      ),
      Text(" Login in ... Please wait")
    ],
  );

  void _showPassword() {
    setState(() {
      _invisiblePass = !_invisiblePass;
    });
  }

  @override
  Widget build(BuildContext context) {

    //// user email ////
    TextFormField userName() => TextFormField(
          key: const Key('login username input'),
          autofocus: false,
          keyboardType: TextInputType.emailAddress,
          controller: usernameController,
          validator: validateEmail,
          onSaved: (value) => _userName = value!.trim(),
          textInputAction: TextInputAction.next,
          style: AppTheme.body1WTextStyle,
          decoration: buildInputDecoration(
            'Enter Email',
            Icons.email,
            lightWTextColor.withOpacity(0.4),
          ),
          // focusNode: _usernameFocusNode,
          // onFieldSubmitted: (String val) {
          //   final focusNode = FocusNode();
          //   focusNode.unfocus();
          // },
        );

    //// user password ////
    TextFormField userPassword() => TextFormField(
          key: const Key('login password input'),
          obscureText: !_invisiblePass,
          keyboardType: TextInputType.visiblePassword,
          controller: passwordController,
          validator: validatePassword,
          onSaved: (value) => _password = value!.trim(),
          textInputAction: TextInputAction.done,
          style: AppTheme.body1WTextStyle,
          decoration: buildInputDecoration(
            'Enter Password',
            Icons.vpn_lock,
            lightWTextColor.withOpacity(0.4),
          ).copyWith(
            suffixIcon: GestureDetector(
              onTap: () {
                _showPassword();
              },
              child: Icon(
                _invisiblePass ? Icons.visibility : Icons.visibility_off,
                color: Colors.black54,
              ),
            ),
          ),
        );

    final forgotLabel = Padding(
      padding: const EdgeInsets.all(0.0),
      child: Container(
        alignment: Alignment.topRight,
        child: TextButton(
          child: const Text(
            "Forgot password?",
            style: AppTheme.body1WTextStyle,
          ),
          onPressed: () {
            Navigator.of(context).pushNamed(passwordResetScreenRoute);
          },
        ),
      ),
    );

    final signupLabel = Padding(
      padding: const EdgeInsets.all(10.0),
      child: TextButton(
        child: const Text(
          "Sign Up for an Account",
          style: AppTheme.subTitleWTextStyle,
        ),
        onPressed: () {
          Navigator.of(context).pushNamed(
            userEditScreenRoute,
            arguments: eProfile.addProfile,
          );
        },
      ),
    );

    final loginButton = ButtonWidget(
      key: const Key('login button'),
      text: 'LOG IN',
      btnColor: accentColor,
      borderColor: accentColor,
      textColor: lightWTextColor,
      onPressed: () {
        Navigator.of(context).pushReplacementNamed(homeScreenRoute);
        // _submit();
      },
    );

    final loginForm = Form(
      key: _userLoginFormKey,
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          userName(),
          const SizedBox(
            height: 10.0,
          ),
          userPassword(),
          forgotLabel,
          const SizedBox(
            height: 10.0,
          ),
          loginButton,
          const SizedBox(
            height: 10.0,
          ),
          signupLabel,
        ],
      ),
    );

    final mainBody = InkWell(
      onTap: () {
        FocusScope.of(context).requestFocus(FocusNode());
      },
      child: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        decoration: wBackground(),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 10.0),
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image.asset(
                  'assets/images/_logo.png',
                  height: 200.0,
                ),
                Expanded(
                  flex: 1,
                  child: loginForm, //Text('this text here'),
                ),
              ],
            ),
          ),
        ),
      ),
    );

    return SafeArea(
      child: Scaffold(
        body: SingleChildScrollView(
          child: mainBody,
        ),
      ),
    );

  }
}

and when I try to navigate to Home Screen on tap of login button, the test fails.

my test case is like this

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  //
  // start.main();
  login.main();
}

//
void main() {
  doLoginTest();
}

void doLoginTest() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets("Login in test run", (WidgetTester tester) async {
    //
    pawfect.main();
    await tester.pumpAndSettle(const Duration(seconds: 3));
    //test here

    final Finder login =
        find.byWidgetPredicate((widget) => widget is LoginScreen);
    expect(login, findsOneWidget);
    await tester.pumpAndSettle(const Duration(seconds: 1));
    //
    var emailInput = find.byKey(const Key('login username input'));
    await tester.tap(emailInput);
    await tester.enterText(emailInput, "test@m.com");
    await tester.pumpAndSettle(const Duration(seconds: 1));
    //
    var passwordInput = find.byKey(const Key('login password input'));
    await tester.tap(passwordInput);
    await tester.enterText(passwordInput, "password");
    await tester.pumpAndSettle(const Duration(seconds: 1));
    //
    var loginButton = find.byKey(const Key('login button'));
    await tester.tap(loginButton, warnIfMissed: false);
    await tester.pumpAndSettle(const Duration(seconds: 3));
    //
    // expect(version, findsOneWidget);
    // final Finder home = find.byWidget(const HomeScreen());
    expect(find.byWidgetPredicate((widget) => widget is HomeScreen),
        findsOneWidget);
    // await tester.pumpAndSettle(const Duration(seconds: 1));

    var version = find.byWidgetPredicate(
        (widget) => widget is Text && widget.data!.contains("Version: 2.0"));
    expect(version, findsOneWidget);
    await tester.pumpAndSettle(const Duration(seconds: 3));
  });
}

what am I doing wrong here? I tried to look for something helpful over the internet and in the docs but I couldn't get my hands dirty enough. Will someone please help me to write a fine Integration test move in with screen to another screen. Thank you so much in advance.

Jason Waku
  • 273
  • 7
  • 20

1 Answers1

0

Do you still have both main methods in your test file? If so can you remove the first (I don't understand how that can even be there):

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  //
  // start.main();
  login.main();
}

Also try stepping through your code - add a breakpoint in your test file and with that test file still in the editor press F5 ( I am assuming here you are in VSCode like me ), find out which expect call is reporting the failure - I'm guessing it is the second:

expect(find.byWidgetPredicate((widget) => widget is HomeScreen),
        findsOneWidget);

Try adding this code before that call (instead of your existing pumpAndSettle call):

  await tester.pump(const Duration(milliseconds: 4000));
  await tester.pumpAndSettle();

Also consider some the ideas in this answer: https://stackoverflow.com/a/70472212/491739

lost baby
  • 3,178
  • 4
  • 32
  • 53