A month of Flutter: testing forms

With the new user registration form in place, it's time to make sure the form is tested and will work as expected.

There are basically five different states that need to be tested.

Default state

This is the view users will first arrive to and here I'm testing that all the components are present as expected.

testWidgets ( 'Renders' , ( WidgetTester tester ) async { await tester . pumpWidget ( app ); expect ( find . text ( 'Register' ), findsOneWidget ); expect ( find . text ( 'I agree to the Terms of Services and Privacy Policy' ), findsOneWidget ); expect ( find . byType ( TextFormField ), findsNWidgets ( 2 )); expect ( find . byType ( OutlineButton ), findsOneWidget ); expect ( find . byType ( Checkbox ), findsOneWidget ); });

Submitted

To succesfully submit the form, I require the user to provide a nickname and a full name. In the test those values will be provided with enterText . After filling out the two TextFormFields and submitting the form, I wait a tick for the success SnackBar to render.

testWidgets ( 'Form can be submitted' , ( WidgetTester tester ) async { await tester . pumpWidget ( app ); final Finder nickname = find . widgetWithText ( TextFormField , 'Nickname' ); final Finder fullName = find . widgetWithText ( TextFormField , 'Full name' ); final Finder submit = find . widgetWithText ( OutlineButton , 'Register' ); expect ( find . text ( 'Form submitted' ), findsNothing ); await tester . enterText ( nickname , 'Jess' ); await tester . enterText ( fullName , 'Jess Sampson' ); await tester . tap ( submit ); await tester . pump (); expect ( find . text ( 'Form submitted' ), findsOneWidget ); });

The success SnackBar is a temporary placeholder so that I have an in-app confirmation the form was submitted. Once user registration logic is in place this messaging to the user will change.

void _submit ( ) { if ( _formKey . currentState . validate ()) { const SnackBar snackBar = SnackBar ( content: Text ( 'Form submitted' )); Scaffold . of ( context ). showSnackBar ( snackBar ); } }

Required fields

Next there are two tests to make sure the nickname and full name fields are required. This checks that the required message was displayed and that the success message was not displayed.

testWidgets ( 'Form requires nickname' , ( WidgetTester tester ) async { await tester . pumpWidget ( app ); final Finder submit = find . widgetWithText ( OutlineButton , 'Register' ); await tester . tap ( submit ); await tester . pump (); expect ( find . text ( 'Nickname is required' ), findsOneWidget ); expect ( find . text ( 'Form submitted' ), findsNothing ); });

There are some improvements that could be made this error display. If a user focuses an errored TextFormField and enters a letter, the error message should disappear. I've created an issue to implement this in the future.

Disabled submit

If a user disables the Terms of Service/Privacy Policy checkbox, they are not longer permitted to register. Here I am testing that the submit button is disabled if the checkbox is unchecked.

The WidgetTester#widget method is a way to get a reference to a finder's actual widget. This is how I'm testing to see if the submit button is disabled. Usually you should test UI that the user can see but in this case the state of the button is conveyed to the user through its styling.

testWidgets ( 'Submit disabled if TOS unchecked' , ( WidgetTester tester ) async { await tester . pumpWidget ( app ); final Finder submit = find . widgetWithText ( OutlineButton , 'Register' ); final Finder tos = find . byType ( Checkbox ); expect ( tester . widget < OutlineButton >( submit ). enabled , isTrue ); await tester . tap ( tos ); await tester . tap ( submit ); await tester . pump (); expect ( tester . widget < OutlineButton >( submit ). enabled , isFalse ); expect ( find . text ( 'Form submitted' ), findsNothing ); });

Code changes

Posts in this series