A month of Flutter: testing forms
Tags: flutter, tutorial, testing, forms
Published: 2018-12-23 20:21:47

_Originally published on [bendyworks.com](https://bendyworks.com/blog/a-month-of-flutter-testing-forms)._

With the new [user registration form](https://bendyworks.com/blog/a-month-of-flutter-user-registration-form) in place, it's time to make sure the form is [tested](https://flutter.io/docs/cookbook/testing/widget/tap-drag) 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.

~~~dart
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`](https://docs.flutter.io/flutter/flutter_test/WidgetTester/enterText.html). After filling out the two `TextFormFields` and submitting the form, I wait a tick for the success `SnackBar` to render.

~~~dart
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.

~~~dart
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.

~~~dart
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](https://github.com/abraham/birb/issues/55) 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](https://docs.flutter.io/flutter/flutter_test/WidgetController/widget.html) 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.

~~~dart
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

- [#54 Add registration form](https://github.com/abraham/birb/pull/54)