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.
Example
In the following example, the HomePage
widget depends on the UserProvider
to display the user's name.
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";
}
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:
App
└── UserProvider
└── HomePage
└── UserTile
└── Consumer<UserProvider>
└── Text
Problem
If the UserTile
is cataloged using the following code
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
.
WidgetbookApp
└── UserTile
└── Consumer<UserProvider> ❌ Error: UserProvider not found
└── Text
To catalog the UserTile
widget, you need to do one of the following:
- Remove the dependency of the
UserTile
by using property extraction. - 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.
class UserTile extends StatelessWidget {
const UserTile({
super.key,
required this.user,
});
final String user;
@override
Widget build(BuildContext context) {
return Text(user);
}
}
@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
App
└── UserProvider
└── HomePage
└── Consumer<UserProvider>
└── UserTile
└── Text
The Widget tree in Widgetbook will also change accordingly
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.It might feel weird seeing
mocktail
used as adependency
and not adev_dependency
, but the wholewidgetbook
app is a dev tool app.dependencies: # ... mocktail: ^1.0.0
-
Mock
UserProvider
in the use-case builder function as followsclass 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
App
└── UserProvider
└── HomePage
└── UserTile
└── Consumer<UserProvider>
└── Text
But the Widget tree in Widgetbook will change according to your mocking
WidgetbookApp
└── MockUserProvider
└── UserTile
└── Consumer<UserProvider>
└── Text