widget_driver_test 1.0.4 copy "widget_driver_test: ^1.0.4" to clipboard
widget_driver_test: ^1.0.4 copied to clipboard

Contains helper classes/methods for DrivableWidgets, WidgetDrivers, helps with TestDrivers mocking


pub package check-code-quality License

widget_driver_test is a package that makes testing WidgetDrivers and DrivableWidgets easy. Built to work with mocktail for easy mocking.

To learn more about WidgetDrivers then please read the documentation for widget_driver.

For inspiration on how to use this package to test your widgets and drivers, then please see the tests in the example app here.


Testing DrivableWidgets #

Create a mock driver #

When you are testing your drivable widgets, then you probably want to mock the driver which drives them.

To make it easier to mock the drivers, we have created a base mock class which you can extend. This gives you functionality from mocktail that you can use on your mock instance.

import 'package:mocktail/mocktail.dart';

...

class MockMyWidgetDriver extends MockDriver implements MyWidgetDriver {}

...

mockMyWidgetDriver = MockMyWidgetDriver();
when(() => mockMyWidgetDriver.title).thenReturn('Hey this is a mocked title');

Using a mock driver in DrivableWidget #

When you have created your mocked driver, then you also want to use it in your widget.

To make it easy to provide a mocked driver into a drivable widget, we have created a helper class which can provide this.

Create an instance of MockDriverProvider and pass it the mocked driver as value and the drivable widget as a child.

final myWidget = MockDriverProvider<MyWidgetDriver>(
    value: mockMyWidgetDriver,
    child: MyWidget(),
);

await tester.pumpWidget(myWidget);
// Do your widget testing now

Use real drivers in your DrivableWidgets #

In case you have some special use case where you actually don't want to use TestDrivers then you can use the WidgetDriverTestConfigProvider. It gives you the possibility to control if a TestDriver or a real driver is created for each DrivableWidget. Just wrap the widget under tests inside a WidgetDriverTestConfigProvider.

NOTE:
It really should be an exception to force the use of real drivers in your tests. Since most of the time, when you are testing a DrivableWidget, then you actaully want to abstract away the real implemention logic in any child DrivableWidget, and only focus on testing the current DrivableWidget and its direct logic.

If you want to use real drivers for all DrivableWidgets in a test, then you can do this:

final myWidget = WidgetDriverTestConfigProvider(
  config: AlwaysUseRealDriversTestConfig(),
  child: MyWidget(),
);
await tester.pumpWidget(myWidget);

If you only want to use real drivers for some of your DrivableWidgets, then you can do this:

final myWidget = WidgetDriverTestConfigProvider(
  config: UseRealDriversForSomeTestConfig(
    useRealDriversFor: { MyWidgetDriver }
  ),
  child: MyWidget(),
);
await tester.pumpWidget(myWidget);

Testing WidgetDrivers #

To test your Drivers you need your tests to use the testWidgets test function (just like you do when you test widgets)

void main() {
    testWidgets('Some driver test', (WidgetTester tester) async {
        // Put your driver test code here
    }
}

Create a driver #

To create your driver you will need a helper function.
This is because the Driver needs to be constructed in the correct way by the widget_driver framework. If you would just create an instance of the driver yourself then some parts initialization phase and the lifecycle management of the driver will not work.

To help you with this we have created a helper function on the WidgetTester.

This is how you create your Driver:

testWidgets('Some driver test', (WidgetTester tester) async {
    final driverTester = await tester.getDriverTester<MyWidgetDriver>(
          driverBuilder: () => MyWidgetDriver(theService: mockTheService),
          parentWidgetBuilder: (driverWidget) {
            return MultiProvider(
              providers: [
                Provider<SomeService>.value(value: mockSomeService),
                Provider<AnotherService>.value(value: mockAnotherService),
              ],
              child: driverWidget,
            );
          });
}

As you can see, you create the Driver by calling tester.getDriverTester(...).
This will return you a DriverTester. You use this driverTester to test your driver.

In this example, we are creating a driver called MyWidgetDriver. It has some internal dependencies which it resolves from the BuildContext (SomeService and AnotherService) and one dependency which gets passed in as a parameter to the constructor.

In the driverBuilder you pass a builder which creates your Driver. There you can provide all mocked dependencies which you pass in via the constructor. E.g. the theService.

The other two dependencies needs to be in the build context when the driver gets created. So we need to put the mocked versions of these dependencies in a widget above the Driver. This is done via the optional parentWidgetBuilder parameter to the getDriverTester method.

There you can pass in a widget which then takes a driverWidget as a child. This driverWidget is the widget which contains the Driver.

In our example we pass in our mocked services by using the Provider package.

Testing a Driver #

Once you have access to the DriverTester, then you can use it to test the Driver.
The driverTester has a property called driver. This gives you access to an instance of your Driver. This is the driver that you will use during your tests.

testWidgets('Some driver test', (WidgetTester tester) async {
    final driverTester = await tester.getDriverTester<MyWidgetDriver>(...)

    final driver = driverTester.driver;
    expect(driver.buttonText, equals('The expected text'));

If your driver updates the widget when some of its dependencies change state, then you easily test this also by awaiting calls to the notifyWidget().

testWidgets('When isLoggedInStream emits then notifyWidgets is called', (WidgetTester tester) async {
    final driverTester = await tester.getDriverTester<LogInOutButtonDriver>(...)

    isLoggedInStreamController.add(true);
    isLoggedInStreamController.add(false);

    // Wait for the driver to receive 2 notifyWidget calls.
    await driverTester.waitForNotifyWidget(numberOfCalls: 2, requireExactNumberOfCalls: true);
    // Verify no more calls to `notifyWidget`
    await driverTester.verifyNoMoreCallsToNotifyWidget();

Here we have a driver which has an internal dependency to a some auth service. Whenever the auth service changes the logged in state, then the driver will call the notifyWidgets() an update the widget.

We want to verify that the driver really calls notifyWidgets() and to do this we can use a helper function on the driverTester.

There are two functions which helps us here.
First the waitForNotifyWidget will wait until the specified number of calls to notifyWidgets() have been reached. If you never get enough calls, then the waitForNotifyWidget will timeout and your test will fail. You can pass in the timeout duration as a parameter to the method. It defaults to 1 seconds.

Second, you can use the verifyNoMoreCallsToNotifyWidget to wait and check that no more calls are made to the notifyWidgets(). You can control how long the method will wait and check for call by passing in a timeout duration to the method. The default value is 1 second.

2
likes
140
points
96
downloads

Publisher

verified publisherbmwtech.dev

Weekly Downloads

Contains helper classes/methods for DrivableWidgets, WidgetDrivers, helps with TestDrivers mocking

Repository (GitHub)
View/report issues
Contributing

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_test, meta, mocktail, test, widget_driver

More

Packages that depend on widget_driver_test