Skip to content

How to Widget Test (Work In Progress)

Samuel Montambault edited this page May 22, 2021 · 4 revisions

⚠️ THIS PAGE IS A WORK IN PROGRESS ⚠️

Testing help reduces bugs and so increase considerably the stability of the application. And because everything is a widget in Flutter it's important to test these Widgets!

Before going any further, we recommend reading the Flutter series about widget testing.

In the next sections, we are going to explain how we test widget in this project.

Mock a service/manager

Widget testing is like a unit test for widgets so it's very important to test the widget on its own, without any services or manager acting. So to avoid any services/managers the widget could use we have to mock them using mockito. Here is the process:

1. Create the mock

Under the folder test/mock/services/ create a file called NAME_OF_THE_SERVICE_mock.dart and use the template below:

// FLUTTER / DART / THIRD-PARTIES
import 'package:mockito/mockito.dart';

// SERVICE
import 'package:notredame/core/services/service_file.dart';

// Mock for the NAME OF THE SERVICE service
class NameOfTheServiceMock extends Mock implements NameOfTheService {}

It's also possible to create a mock that will stub a function. Stubbing is a mock behavior when a test function wants to expect a specific return from a function (or an exception). We can stub a function like this example from :

// FLUTTER / DART / THIRD-PARTIES
import 'package:mockito/mockito.dart';

// CONSTANT
import 'package:notredame/core/constants/preferences_flags.dart';

// SERVICE
import 'package:notredame/core/services/preferences_service.dart';

class PreferencesServiceMock extends Mock implements PreferencesService {
  /// Stub the answer of [setString] when the [flag] is used.
  static void stubSetString(PreferencesServiceMock mock, PreferencesFlag flag,
      {bool toReturn = true}) {
    when(mock.setString(flag, any)).thenAnswer((_) async => toReturn);
  }
}

Now if this method is called, the bool toReturn passed in parameter will be returned everytime the mock call setString on any flag.

2. Add the function to register this service

We are using GetIt as service locator, so if the widget to test use services these had to be registered into the locator and so the mocked services! To register a mock on demands you have to add a new function into the helpers file following this template:

// OTHER
import 'package:notredame/locator.dart';

// SERVICES
import 'package:notredame/core/services/service_file.dart';

// MOCKS
import 'mock/services/name_of_the_service_mock.dart';

/// Load a mock of the [ServiceName]
ServiceName setupServiceNameMock() {
  unregister<ServiceName>();
  final service = ServiceNameMock();
  
  locator.registerSingleton<ServiceName>(service);

  return service;
}

3. Use the mock into a test file!

To complete.

Troubleshooting

In this section we discuss common error that occurs during testing and how to prevent it.

  • Exception Could not find a FeatureDiscovery

This exception will usually look like that:

════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building IconTheme(color: Color(0x8a000000), size: 24.0):
Could not find a FeatureDiscovery widget above this context.
FeatureDiscovery works like an inherited widget. You must wrap your widget tree in it.
Ensure that all the DescribedFeatureOverlay widgets are below it.

The best way to get rid of it is by wrapping the FeatureDiscovery widget around the widget you are trying to pump into the test.

await tester.pumpWidget(localizedWidget(child: FeatureDiscovery(child: MoreView())));
await tester.pumpAndSettle();