Flutter Null Safety Unit Tests: Late for Tests

Dart added the late keyword as part of their null safety release and it is very useful, particularly for testing. As a quick review null safety means telling the compiler that some variables will never be null. Any variable declared without a question mark following the type declaration is null safe. So String name is null safe while String? name can be null. Given that the question mark is new syntax all code written before null safety was introduced now declares all variables as cannot be null. The new late keyword tells the compiler that although a variable is not declared null safe, in practice it will never be null when accessed. Code is then added at runtime to ensure that the variable is not null when used. The compiler cannot perform any checks for you, so it’s not as good as full null safety but it can be convenient, particularly for tests. An example should make it clearer.

Example

Before null safety here is a common pattern that I used in unit tests to ensure that the class is reinitialized before each test while not having to put the constructor in the test.

import 'package:flutter_test/flutter_test.dart';

class Accumulator {
  int _currentTotal = 0;

  int add(int val) => _currentTotal += val;
  int get currentTotal => _currentTotal;
}

void main() {
  Accumulator accumulator;

  setUp(() {
    accumulator = Accumulator();
  });

  test('starts at 0', () async {
    expect(accumulator.add(2), 2);
  });

  test('accumulates', () async {
    expect(accumulator.add(2), 2);
    expect(accumulator.add(2), 4);
  });
}

Null Safe Version

This code won’t compile with sound null safety because accumulator isn’t declared as nullable and isn’t initialized. The simplest way to fix that would be to make it nullable by changing the declaration to:

Accumulator? accumulator;

However, that means adding either ? or ! to every use. The new late keyword offers an excellent alternative. Change the declaration to:

late Accumulator accumulator;

The compiler is now happy and if accumulator is ever not initialized before use it will be caught at runtime because Dart inserts runtime checks around every use of that variable to ensure it can’t be null. While late is very convenient in tests, it’s probably not the best construct to use for null safety in general because the compiler can’t help as much and any errors will occur while the application is in use. For test cases the convenience is a big win, and if a variable happens to not be initialized the test case will fail.

Leave a Reply