Mocking

Adding a use-case for a widget that has external dependencies can be challenging. In this guide, we'll explore how to mock these dependencies to make cataloging the widget easier.

Video tutorial

Example

In the following example, the HomePage widget depends on the UserProvider to display the user's name.

dart
class UserProvider with ChangeNotifier {
  // For simplicity, we're hardcoding the user's name.
  // In a real-world scenario, this would be fetched from an API or a database.
  String user => "John Doe";
}
dart
class UserTile extends StatelessWidget {
  const UserTile({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<UserProvider>(
      builder: (context, provider, child) {
        return Text(provider.user);
      },
    );
  }
}

For this example, the Widget tree for your HomePage widget in your app might look like this:

tree
App
└── UserProvider
    └── HomePage
        └── UserTile
            └── Consumer<UserProvider>
                └── Text

Problem

If the UserTile is cataloged using the following code

dart
Widget userTileUseCase(BuildContext context) {
  return UserTile();
}

Flutter throws an error indicating that a UserProvider is missing from the Widget tree, as the Consumer within the UserTile depends on the UserProvider.

tree
WidgetbookApp
└── UserTile
    └── Consumer<UserProvider>         ❌ Error: UserProvider not found
        └── Text

To catalog the UserTile widget, you need to do one of the following:

  1. Remove the dependency of the UserTile by using property extraction.
  2. Provide the UserProvider to the Widget tree.

Approach I: Extraction

The simplest method to catalog UserTile is to extract the UserProvider dependency into a parameter.

dart
class UserTile extends StatelessWidget {
  const UserTile({
    super.key,
    required this.user,
  });

  final String user;

  @override
  Widget build(BuildContext context) {
    return Text(user);
  }
}
dart
@widgetbook.UseCase(name: 'Primary', type: UserTile)
Widget buildUserTile(BuildContext context) {
  return UserTile(
    user: 'John'
  );
}

Extracting the dependency into a parameter will also change your Widget tree structure of your app

tree
App
└── UserProvider
    └── HomePage
        └── Consumer<UserProvider>
            └── UserTile
                └── Text

The Widget tree in Widgetbook will also change accordingly

tree
WidgetbookApp
└── UserTile
    └── Text

Approach II: Mocking Libraries

Not in all cases, you can extract the dependency. In some case you need to mock the dependency, for example if you are cataloging a "screen" widget.

Add a mocking library to your widgetbook/pubspec.yaml file.

yaml
dependencies:
  # ...
  mocktail: ^1.0.0

Mock UserProvider in the use-case builder function as follows

dart
class MockUserProvider extends Mock implements UserProvider {}

@widgetbook.UseCase(name: 'Primary', type: UserTile)
Widget buildUserTile(BuildContext context) {
 return ChangeNotifierProvider<UserProvider>(
   create: (_) {
     final provider = MockUserProvider();
     when(() => provider.user).thenReturn('Mocked User');

     return provider;
   },
   child: UserTile(),
 );
}

In this approach, the Widget tree of your app stays the same

tree
App
└── UserProvider
    └── HomePage
        └── UserTile
            └── Consumer<UserProvider>
                └── Text

But the Widget tree in Widgetbook will change according to your mocking

tree
WidgetbookApp
└── MockUserProvider
    └── UserTile
        └── Consumer<UserProvider>
            └── Text