Closed rubiktubik closed 5 years ago
Hello!
You need to add the necessary providers in the widget tree passed to pumpWidget
:
await tester.pumpWidget(
Provider.value<MyViewModel>(
value: someMock,
child: MyWidget(),
),
)
Thank you for the quick answer! I'am using mockito to mock my ViewModel but i get
The following ProviderNotFoundError was thrown building Consumer<MyViewModel>(dirty):
Error: Could not find the correct Provider<MyViewModel> above this
Consumer<MyViewModel> Widget
In the pumpWidgetCode i only changed Provider to ChangeNotifierProvider:
class MockMyViewModel extends Mock implements MyViewModel{}
...
Widget createWidgetForTesting({Widget child}) {
return MaterialApp(
home: child,
);
}
...
final mockVm = MockMyViewModel();
await tester.pumpWidget(ChangeNotifierProvider.value(
value: mockVm,
child: createWidgetForTesting(child: MyWidget()),
));
The createWidgetForTesting is for using MediaQuery in my widget.
How can i make i work?
You need to manually specify the object type when passed to provider:
DON'T :
Provider.value(
value: FooMock()
)
DO:
Provider<Foo>.value(
value: FooMock(),
)
Now it works! Thanks!
I'll keep this open as a reminder to document that behavior
@rrousselGit, Thanks too! I did this successfully only after finding this issue. Maybe a complete example can be documented somewhere?
BUT, I can't get my model to notify my listeners/consumers. I'm mocking the model's method that contains notifyListeners();
since it's mocked, I called the mocked model's notifyListeners
method manually, however, the Consumer did not rebuild the widget.
Sample snippet in my tests
final auth = MockAuth();
when(auth.check()).thenReturn(false);
when(auth.signIn(email: email, password: password))
.thenAnswer((_) async => FakeUser());
await tester.pumpWidget(App(
auth: auth, // App widget builds ChangeNotifierProvider<Auth>.value(value: widget.auth,child: MaterialApp(...
childAuth: Placeholder(),
childGuest: OnboardingRoute(),
));
// ...log in steps here in the onboarding route since auth.check is false. A method in my widget calls auth.signIn() which contains notifyListeners() which will not trigger since it's mocked
reset(auth);
when(auth.check()).thenReturn(true);
auth.notifyListeners();
await tester.pump(); // does nothing, but I think it should still trigger from the Consumer listening to Auth/MockAuth
expect(find.byType(Placeholder), findsOneWidget); // ...MaterialApp(child: Consumer<Auth>(builder: (context, auth, child) => auth.check() ? widget.childAuth : widget.childGuest)...
Did you mock notifyListeners
itself inadvertently?
I don't think so... I didn't do when(auth.notifyListeners).then...()
. Here are a few more snippets for additional context.
// in tests
class MockAuth extends Mock implements Auth {}
// the auth model
class Auth extends ChangeNotifier {
User _currentUser;
bool check() {
return _currentUser != null;
}
Future<User> signIn(
{@required String email, @required String password}) async {
assert(email != null);
assert(password != null);
// ...log in logic here
notifyListeners();
return _currentUser;
}
}
Even if you didn't do when(auth.notifyListener
, notifyListeners
is still mocked.
Not only that, but addListeners
/removeListeners
are mocked too.
If you want to mock a ChangeNotifier, you can't just do extends Mock implements MyNotifier
. You'll need a custom implementation.
That makes it more complicated to test my widget. Is there any easy workaround? I just need Consumer<Auth>
to rebuild and call auth.check()
. If not, can you give an example of a custom implementation? Did you mean a Fake instead of a Mock? That's a lot of functions to override.
You could extend the true notifier and override check
only:
class AuthSpy extends Auth {
final checkSpy = OnAuthCheck();
@override
bool check() => checkSpy();
}
class OnAuthCheck extends Mock {
bool check();
}
That works. Thank you very much! For those who are interested, here's how I got my specific code to work based on my snippets above.
// on top of the test file
class SpyAuth extends Auth {
SpyAuth({@required this.checkSpy}) : assert(checkSpy != null);
final SpyAuthCheck checkSpy;
@override
bool check() => checkSpy.check();
@override
Future<User> signIn(
{String email, String password}) async {
notifyListeners();
return User();
}
}
class SpyAuthCheck extends Mock {
bool check();
}
// in the actual test
final spyAuthCheck = SpyAuthCheck();
final spyAuth = SpyAuth(checkSpy: spyAuthCheck);
when(spyAuthCheck.check()).thenReturn(false);
await tester.pumpWidget(App(
auth: spyAuth,
childAuth: Placeholder(),
childGuest: OnboardingRoute(),
));
// ...log in steps here
reset(spyAuthCheck);
when(spyAuthCheck.check()).thenReturn(true);
// ... tap on log in button here. it will call spyAuth.signIn()
await tester.pump(); // or pumpSettle, depending on your widget hierarchy
expect(find.byType(Placeholder), findsOneWidget); // success!
Hi, I have this issue on console:
The following ProviderNotFoundException was thrown running a test:
Error: Could not find the correct Provider<AuthProvider> above this UserInfoScreen Widget
class MockAuthProvider extends Mock implements AuthProvider {}
void main() {
Widget makeTestableWidget({Widget child}) {
return MaterialApp(home: child);
}
testWidgets('Test widget', (WidgetTester tester) async {
final mock = MockAuthProvider();
when(mock.getFoo()).thenReturn('Foo');
// ASSEMBLE
await tester.pumpWidget(
ChangeNotifierProvider.value(
value: mock,
child: makeTestableWidget(
child: UserInfoScreen(),
),
),
);
});
}
class UserInfoScreen extends StatefulWidget {
@override
_UserInfoScreenState createState() => _UserInfoScreenState();
}
class _UserInfoScreenState extends State<UserInfoScreen> with BaseWidgetState {
AuthProvider _provider;
@override
void initState() {
super.initState();
_provider = context.read<AuthProvider>();
final foo = _provider.getFoo();
}
}
@MaxTenco Make sure to type your provider as ChangeNotifierProvider<AuthProvider>
otherwise the type inference uses ChangeNotifierProvider<MockAuthProvider>
, which isn't what you want
Hello!
Im trying to test one of my widgets that calls a function in the provider.
Widget i wanna test:
class CategoriesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final categoryData = Provider.of<Categories>(context).items;
return (categoryData == null)
? Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
//padding: const EdgeInsets.all(25),
children: categoryData
.map(
(categoryData) => CategoryItem(categoryData.id,
categoryData.title, categoryData.bgimage),
)
.toList(),
),
),
);
}
}
The provider with the function:
class Categories with ChangeNotifier {
List<Category> _items = [
Category(
id: 'c1',
title: 'Akció',
bgimage: 'https://filmrakat.hu/wp-content/uploads/2018/10/mile22-01.jpg',
),
Category(
id: 'c2',
title: 'Animációs',
bgimage:
'https://i0.wp.com/nypost.com/wp-content/uploads/sites/2/2019/09/tv-scooby-doo-2b.jpg?quality=80&strip=all&ssl=1',
),
Category(
id: 'c3',
title: 'Zenés',
bgimage:
'https://www.mafab.hu/static/2018t/273/21/10676_1538422121.6285.jpg',
),
Category(
id: 'c4',
title: 'Fantasy',
bgimage:
'https://sm.ign.com/ign_hu/screenshot/default/harry-potter_3nyx.jpg',
),
Category(
id: 'c5',
title: 'Horror',
bgimage:
'https://news.ucdenver.edu/wp-content/uploads/2019/10/Horror-Film-1288x726.jpg',
),
Category(
id: 'c6',
title: 'Sci-fi',
bgimage:
'https://ectopolis.hu/wp-content/uploads/intelligens-sci-fi-filmek-06.jpg',
),
Category(
id: 'c7',
title: 'Vígjáték',
bgimage:
'https://www.mafab.hu/static/2019t/168/13/1974_1560855623.0691.jpg',
),
Category(
id: 'c8',
title: 'Romantikus',
bgimage:
'https://shop.movar-print.hu/wp-content/uploads/2019/03/rozsak-vaszonkep.jpg',
),
Category(
id: 'c9',
title: 'Mese',
bgimage:
'https://filmrakat.hu/wp-content/uploads/2019/09/clara-tunderi-kaland-film-03-720x450.jpg',
),
Category(
id: 'c10',
title: 'Háborús',
bgimage: 'https://media.port.hu/images/001/106/484.jpg',
),
];
List<Category> get items {
return [..._items];
}
...
The test:
class MockCategories extends Mock implements Categories {}
Categories categories;
final categoriesResponse = [
Category(
id: 'c1',
title: 'Akció',
bgimage: 'https://filmrakat.hu/wp-content/uploads/2018/10/mile22-01.jpg',
),
Category(
id: 'c2',
title: 'Animációs',
bgimage:
'https://i0.wp.com/nypost.com/wp-content/uploads/sites/2/2019/09/tv-scooby-doo-2b.jpg?quality=80&strip=all&ssl=1',
)
];
...
testWidgets('CategoryScreen', (WidgetTester tester) async {
final categories = MockCategories();
await tester.pumpWidget(ChangeNotifierProvider<Categories>.value(
value: categories,
child: MaterialApp(home: CategoriesScreen()),
));
await tester.pump();
expect(find.byType(CircularProgressIndicator), findsNothing);
});
What im doing wrong? Thanks for the answer in advance!
Hi, i'am using the great provider package in my project.
I was about to write a widget test. And there is no documentation how to that with provider.
I have my widget:
And i have my definition of providers:
And i have my test:
How can i test my widget, with using a mock viewmodel?
Regards Michael