Writing tests¶
All new functionality must be tested, if at all possible. When fixing a bug, tests must be added to ensure that this bug would not reappear in the future.
Run
melos run coverage
to execute all tests in the “coverage” mode. The results will be saved in thecoverage/index.html
file, which can be opened in a browser. Try to achieve 100% coverage for any new functionality added.Every source file should have a corresponding test file, with the
_test
suffix. For example, if you’re making aSpookyEffect
and the source file issrc/effects/spooky_effect.dart
, then the test file should betest/effects/spooky_effect_test.dart
mirroring the source directory.The test file should contain a
main()
function with a singlegroup()
whose name matches the name of the class being tested. If the source file contains multiple public classes, then each of them should have its own group. For example:void main() { group('SpookyEffect', () { // tests here }); }
For a larger class, multiple groups can be created inside the top-level group, allowing to navigate the test suite easier. The names of the nested groups should be capitalized.
The names of the individual tests should normally start with a lowercase.
Often, you would need to define multiple helper classes to run the tests. Such classes should be private (start with an underscore), and placed at the end of the file. The reason for this is that whenever some test breaks, the first thing one needs to do is to go into the test file and run all the tests. Having the
main()
function at the top of the file makes this process much easier.
Types of tests¶
Simple tests¶
test('the name of the test', () {
expect(...);
});
This is the simplest kind of test available, and also the fastest. Use these tests for checking some classes/methods that can function in isolation from the rest of the Flame framework.
FlameGame tests¶
It is very common to want to have a FlameGame
instance inside a test, so that you can add some
components to it and verify various behaviors. The following approach is recommended:
testWithFlameGame('the name of the test', (game) async {
game.add(...);
await game.ready();
expect(...);
});
Here the game
instance that is passed to the test body is a fully initialized game that behaves
as if it was mounted to a GameWidget
. The game.ready()
method waits until all the scheduled
components are loaded and mounted to the component tree.
The time within the game
can be advanced with game.update(dt)
.
If you need to have a custom game inside this test (say, a game with some mixin), then use
testWithGame<_MyGame>(
'the name of the test',
_MyGame.new,
(game) async {
// test body...
},
);
Widget tests¶
Sometimes having a “naked” FlameGame
is insufficient, and you want to have access to the Flutter
infrastructure as well. That is, to have a game mounted into a real GameWidget
embedded into an
actual Flutter framework. In such cases, use
testWidgets('test name', (tester) async {
final game = _MyGame();
await tester.pumpWidget(GameWidget(game: game));
await tester.pump();
await tester.pump();
// At this point the game is fully initialized, and you can run your checks
// against it.
expect(...);
// Equivalent to game.update(0)
await tester.pump();
// Advances in-game time by 20 milliseconds
await tester.pump(const Duration(milliseconds: 20));
});
There are some additional methods available on the tester
controller, for example in order to
simulate taps, or drags, or key presses.
Golden tests¶
These tests verify that things render as intended. The process of creating a golden test is simple:
Write the test, using the following template:
testGolden( 'the name of the test', (game) async { // Set up the game by adding the necessary components // You can add `expect()` checks here too, if you want to }, size: Vector2(300, 200), goldenFile: '.../_goldens/my_test_file.png', );
Here the
size
parameter determines the size of the game canvas and of the output image. ThegoldenFile
parameter is the name of the file where you want to store the “golden” results. This should be a relative path to thetest/_goldens
directory, starting from your test file.Run
flutter test --update-goldens
this would create the golden file for the first time. Open the file to verify that it renders exactly as you intended. If not, then delete the file and go back to step 1.
Subsequent runs of
flutter test
will check whether the output of the golden test matches the saved golden file. If not, Flutter will save the image-diff files into thefailures/
directory where your test is located.
Note
Avoid using text in your golden tests – it does not render reliably across different platforms, due to font discrepancies and differences in anti-aliasing algorithms.
Random tests¶
These are the tests that use a random number generator in order to construct a randomized input and then check its correctness. Use as follows:
testRandom('test name', (Random random) {
// Use [random] to generate random input
});
You can add repeatCount: 1000
parameter to run this test the specified number of times, each one
with a different seed. It is useful to run a high repeatCount
when developing the test, to ensure
that it doesn’t break. However, when submitting the test to the main repository, avoid repeatCounts
higher than 10.
If the test breaks at some particular seed, then that seed will be shown in the test output. Add it
as the seed: NNN
parameter to your test, and you’ll be able to run it for the same seed as long
as you need until the test is fixed. Do not leave the seed:
parameter when submitting your code,
as it defeats the purpose of having the test randomized.