With widget and integration tests, we can ensure the proper functionality of our UI.
Widget tests are meant to test specific widgets we create, and ensure their logic generates the correct visuals and functionality we require.
As this article will show, for our purposes, integration tests are very similar, albeit longer, than widget tests. This is due to the fact that our "pages" are simply larger widgets, some of which link to other widgets when interacted with. However, we still separate them from widget tests, as they are much larger tests aimed at covering the entire flow of a page.
# Widget Tests
<details>
<summary>Making a widget test</summary>
<br>
To start, make a new dart file under the correct package in the test/widget/ directory of the game manager, or create a new directory there if needed. This file should look something like this:
```dart
voidmain(){
testWidgets('This is a test description',(tester)async{
// Your test code
});
}
```
The first function we need to utilize is called `WidgetTester.pumpWidget()` from the flutter_test package. This function will render whichever widget you give it. We need to create the widget we are testing, and give it to this function, so that we can begin using it. For example:
```dart
voidmain(){
testWidgets('NotificationCard has an success message message',(tester)async{
awaittester.pumpWidget(constMaterialApp(
home:NotificationCard(
cardTitle:'Success',
description:'This action was executed successfully.',
success:true,
color:Colors.green,
)));
});
}
```
Note that we have to wrap our widget in the MaterialApp Widget since we can't just render NotificationCard by itself. For readability, I like to abstract the creation of the widget into its own function.
Regardless, we can see that we have set some values of NotificationCard, which determines what the widget will display. We can now assert that the rendered widget has these values with the `expect()` function. While trivial, when more complex logic is added to a widget, this can become useful in ensuring our logic is correct.
```dart
WidgetcreateWidgetSuccess(){
returnconstMaterialApp(
home:NotificationCard(
cardTitle:'Success',
description:'This action was executed successfully.',
success:true,
color:Colors.green,
));
}
voidmain(){
testWidgets('NotificationCard has an success message',(tester)async{
awaittester.pumpWidget(createWidgetSuccess());
expect(find.text('Success'),findsOneWidget);
expect(find.text('This action was executed successfully.'),findsOneWidget);
expect(find.byIcon(Icons.check),findsOneWidget);
});
}
```
In this example, `find` is being used to locate a rendered widget which has the value we are looking for and the second argument in `expect()` asserts that there is only one widget with this value.
As we will see in the integration test example,`find` has multiple methods that can be used for locating your widget, such as `byIcon` which we use, but also `byType`, which takes in a widget's name. You can look in the CommonFinders class of the flutter_test package to see all the possible finders.
You may have noticed we didn't test to see if there was a rendered widget which was green, as we specified the color in the widget creation. This is because there isn't a CommonFinder for color, and thus we need to check the instance variables of the widget to determine if the color was set correctly. We can do that by adding the following line:
This method of parsing the widget as a notification card can be used to check any public variable of the widget, which allows us to assert that the color variable in our widget is in fact green.
With this, we have fully tested a success NotificationCard, as you can see in the codebase, there is another test that tests an error NotificationCard as the NotificationCard Widget has logic based on the success variable we give it in the constructor, ensuring this logic is correct.
</details>
# Integration Tests
As mentioned before, integration tests, for our purposes, are very similar to widget tests. It is important to note, however, that the integration tests in this section, are not "true" integration tests. This is due to two main reasons. Firstly, the flutter `integration_test` package, at the time of writing this, does not support integration tests on all clients. Secondly, as mentioned previously, our pages are actually just larger widgets which point to each other, and don't have their own routes, thus, testing each "page" is achieved through large widget tests.
<details>
<summary>Making an integration test</summary>
<br>
Let's start by making an integration test for the `change_player_password` page.
<details>
<summary>Change Player Password Page Details</summary>
<br>
This page, as the name suggests, is for administrators to change a player's password.
Firstly, the user must select the drop down menu for players, and choose the player whose password they want to change.
Then, they must enter a strong password word with the requirements: 8–16 characters, 1 capital letter, 1 lowercase letter, and 1 special character.
Then, they must enter the same password again into the confirmation box.
Finally, once all three steps have been successfully completed, the submit button at the bottom of the page will become clickable. If any of these steps haven't been completed, the button will be grayed out.
</details>
The first difference between a widget and an integration test will be the mocking of a repository for the widget tree. As you will see, the change player password page has a drop-down menu for selecting a player whose password you want to change. However, this player list is retrieved from the backend; thus, we need to mock the player repository so that when we call `getAllPlayers()` it returns a mocked response.
This code sets up everything we need for this integration test. You will see many similarities to the widget test we created earlier, with some key differences outlined by the comments, like injecting our mock repository.
Note that, as with regular front-end tests, we generate mock classes. The annotation towards the top allows us to specify what mocks we need to generate.
We can then run the following command to generate the `change_password_test.mocks.dart` file:
```
flutter pub run build_runner build
```
One new important function you will see called multiple times is the function `await tester.pumpAndSettle();`. This function triggers a frame (pump) and then waits for everything to finish rendering (settle). Without this, we might try to do things with the tester when our previous actions haven't been completed yet.
Next we are going to ensure that we are on the correct page, find the submit button, and ensure it's onPressed value is null since we haven't completed the steps listed above.
This code first ensures that the drop-down menu is visible and then taps the first one it finds.
Now that we have pressed on the drop-down menu and waited for the render to settle, we can now click on the first `DropdownMenuItem<String>` which is the player our `mockPlayerRepository` gave us when the widget tree was built. Finally, we wait for the selection to settle.
Note that the comment linking to the github issue is referring to, at the time of writing this, an active bug with `flutter_test` which requires us to specify the type of the drop-downs.
This should start to feel familiar. We first find our password field and enter our new password, wait for it to settle, and then we find the confirmation text box and repeat the password and wait for that to settle.
One thing to note is how the `find.ByType()` function works. Since widgets wrap other widgets, when we go to find the confirmation box, we need to select the second `TextField` since our `PasswordField` widget contains its own `TextField`. You will also hear this referred to as the `PasswordField` widget wraps a `TextField` widget.
Finally, we have completed all the steps we talked about at the beginning, meaning we can assert that the submit button should be active.
```dart
// Check to see if the onPressed function is null in the ElevatedButton
With that, we ensure the onPressed value isn't null and complete our integration test.
</details>
# Additional Resources
To see the widget test we made in full, visit the [notification_card_test.dart](https://gitlab.engr.ship.edu/merlin/freshmanrpgsuite/-/blob/main/game_manager/test/widgets/notification_card_test.dart) file in the repository.
To see the integration test we made in full, visit the [change_password_integration_test.dart](https://gitlab.engr.ship.edu/merlin/freshmanrpgsuite/-/blob/main/game_manager/test/integration/change_password_integration_test.dart) file in the repository.