Widget test failing when passing already created bloc instance to provider - flutter

I'm passing the CartBloc instance to the BlocProvider. But the test is failing as the ListView widget is not found.
Below is the test code.
void main() {
late MockCartRepository cartRepository;
late MockSharedPreferenceService sharedPreferenceService;
late AuthBloc authBloc;
late MockUserRepository userRepository;
late CartData cartData;
late CartBloc cartBloc;
setUp(() {
cartData = CartData.fromJson(guestCartData);
cartRepository = MockCartRepository();
sharedPreferenceService = MockSharedPreferenceService();
userRepository = MockUserRepository();
authBloc = AuthBloc(
service: sharedPreferenceService,
userRepository: userRepository,
);
cartBloc = CartBloc(
cartRepository: cartRepository,
sharedPreferenceService: sharedPreferenceService,
authBloc: authBloc,
);
});
_pumpTestWidgetWithEvent(WidgetTester tester, CartEvent event) {
return MaterialApp(
home: BlocProvider(
create: (_) => cartBloc..add(event),
child: const CartView(),
),
);
}
group('Testing cart view', () {
testWidgets(
'Adding item to cart',
(WidgetTester tester) async {
when(cartBloc.cartRepository.addToCart(
cartId: cartData.id!,
quantity: 1,
variantId: cartData.lineItems!.physicalItems!.first.variantId!,
productId: cartData.lineItems!.physicalItems!.first.productId!,
)).thenAnswer((_) async => cartData);
when(sharedPreferenceService.setString('cartId', cartData.id!))
.thenAnswer((_) async => true);
await tester.pumpWidget(
_pumpTestWidgetWithEvent(
tester,
AddToCartEvent(
cartId: cartData.id!,
quantity: 1,
variantId: cartData.lineItems!.physicalItems!.first.variantId!,
productId: cartData.lineItems!.physicalItems!.first.productId!,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Cart'), findsOneWidget);
expect(find.byType(ListView), findsOneWidget);
},
);
});
}
The test passes when I pass a new instance of cart bloc as shown below.
_pumpTestWidgetWithEvent(WidgetTester tester, CartEvent event) {
return MaterialApp(
home: BlocProvider(
create: (_) => CartBloc(
cartRepository: cartRepository,
sharedPreferenceService: sharedPreferenceService,
authBloc: authBloc,
)..add(event),
child: const CartView(),
),
);
}
Why is this happening?
I've also tried using BlocProvider.value and passing cartBloc as value. But this also failing.

Related

Flutter Bloc Widget testing how to find.text under If statement in bloc pattern

I am trying to Widget test my WelcomeScreen(). WelcomeScreen has a BlocProvider and a BlocBuilder. After I load WelcomeBloc() it checks with an if statement inside the builder to check if the state is WelcomeLoadSuccessState.
How do I find something under the if statement if the statement is true?
My Welcome screen:
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WelcomeBloc(),
child: BlocBuilder<WelcomeBloc, WelcomeState>(
builder: (context, state) {
if (state is WelcomeLoadSuccessState) {
return Scaffold(
body: Container(
child: Column(
children: [
Wrap(
direction: Axis.vertical,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(8),
child: ShowUp(
delay: _delay + 200,
child: Text('Welcome user’, // <——— I want to find this one
)),
),
],
),
],
)),
);
}
// return LoadingWidget();
return Text('Something'); // <——— This one I can find
},
),
);
}
The test that I have now:
main() {
WelcomeBloc welcomeBloc;
WelcomeService welcomeService;
final Brand brand = Brand();
setUp(() {
setUpMocks();
welcomeService = localServices<WelcomeService>();
welcomeBloc = MockWelcomeBloc();
});
_createWidget(WidgetTester tester) async {
when(welcomeService.getBrand(id: '609a88d324a01928242d1ca9')).thenAnswer((realInvocation) => Future.value(brand));
welcomeBloc.add(WelcomeLoadRequestEvent(id: '609a88d324a01928242d1ca9'));
when(welcomeBloc.state).thenAnswer((_) => WelcomeLoadSuccessState(brand: brand));
print(welcomeBloc.state); //Correct State (WelcomeLoadSuccessState)
await tester.pumpWidget(
MaterialApp(
title: 'Flutter Demo',
home: WelcomeScreen(),
)
);
await tester.pump();
}
testWidgets('Welcome Screen Test', (WidgetTester tester) async {
await _createWidget(tester);
await tester.pump();
//expect(find.textContaining('Welcome user'), findsOneWidget); //What I want
expect(find.text('Something'), findsOneWidget); //This works
});
tearDown(() {
welcomeBloc?.close();
});
}
Thank you for helping.
I solved it:
change:
create: (context) => WelcomeBloc()
to:
create: (context) => WelcomeBloc()..add(WelcomeLoadRequestEvent(id: '609a88d324a01928242d1ca9')),
and my test is now this:
main() {
WelcomeBloc welcomeBloc;
WelcomeService welcomeService;
final Brand brand = Brand();
setUp(() {
setUpMocks();
welcomeService = localServices<WelcomeService>();
welcomeBloc = MockWelcomeBloc();
});
_createWidget(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
title: 'Flutter Demo',
home: WelcomeScreen(),
));
await tester.pump(Duration(seconds: 10));
}
testWidgets('Welcome Screen Test', (WidgetTester tester) async {
when(welcomeService.getBrand(id: '609a88d324a01928242d1ca9'))
.thenAnswer((realInvocation) => Future.value(brand));
whenListen(
welcomeBloc,
Stream.fromIterable([
WelcomeLoadInProgressState(),
WelcomeLoadSuccessState(brand: brand),
]));
await _createWidget(tester);
await tester.pump(Duration(seconds: 5));
expect(find.textContaining('Welcome user'), findsOneWidget);
});
tearDown(() {
welcomeBloc?.close();
unRegister();
});
}
Edit to add:
For my other pages it was useful to separate the blocProvider and the blocBuilder. This way I was able to Mock my blocProvider with a MockMyBloc() and then give the screen in the child.
My real widgets:
MyWidgetMakeBlocProviders(
Widget build(context) {
return BlocProvider<MyBloc>(
create: (context) => MyBloc(),
child: MyScreen(),
);
}
)
MyScreen(
Widget build(context) {
return BlocBuilder<MyBloc, MyBlocState>(
builder: (context, state) {...}
);
}
)
My test:
testWidgets('', (tester) async {
whenListen(MockMyBloc, Stream.fromIterable([
InitState(),
LoadedState()
]));
await _createWidget(tester);
await tester.pump();
//expect()
});
_createWidget(tester) async {
await tester.pumpWidget(
MaterialApp(
title: '',
home: BlocProvider<MockMyBloc>(
create: (context) => MockMyBloc(),
child: MyScreen(),
)
)
);
await tester.pump();
}

flutter riverpod: how to test asyncvalue.error?

I am having trouble testing my Riverpod FutureProvider when it returns an AsyncValue.error.
I tried the test as shown below:
// Future provider
final ipsProvider = FutureProvider.autoDispose((_) => IpRepository().fetchIps());
// Widget to be tested
class ExampleWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
// hooks
AsyncValue<List<Ip>> ips = watch(ipsProvider);
return Container(
child: ips.when(
data: (data) => randomWidget,
loading: () => progressIndicator,
error: (_, stack) => Text('YesMan'),
),
);
}
}
// Test: I am trying to find a Text widget containing message 'YesMan'
testWidgets('ExampleWidget | error', (WidgetTester tester) async
await tester.pumpWidget(
ProviderScope(
overrides: [
ipsProvider.overrideWithValue(AsyncValue.error('randomErrorMessage')),
],
child: MaterialApp(
home: Builder(builder: (context) {
return ExampleWidget();
}),
),
),
);
final finderError = find.text('YesMan');
expect(finderError, findsOneWidget);
});
I expected the test to return a text widget with message 'randomError', but instead it throws an exception as below:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK The following message was thrown running a test: randomErrorMessage When the exception was thrown, this was the stack:
Any idea on how to test AsyncValue.error cases in Riverpod?
Thank you
instead of overrideWithValue try overrideProvider and run a future.error so FutureProvider catch it
class ExampleWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
// hooks
AsyncValue<String> ips = watch(ipsProvider);
return Container(
child: ips.when(
data: (data) => SizedBox(),
loading: () => CircularProgressIndicator(),
error: (_, stack) => Text('YesMan'),
),
);
}
}
testWidgets('ExampleWidget | error', (WidgetTester tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
ipsProvider.overrideWithProvider(FutureProvider((_) => Future.error('error'))),
],
child: MaterialApp(
home: Builder(builder: (context) {
return ExampleWidget();
}),
),
),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
await tester.pump();
final finderError = find.text('YesMan');
expect(finderError, findsOneWidget);
});

MultiProvider sending NULL in child widgets but prints right value in Console

My HomePagewhere Providers are initilized:
Widget build(BuildContext context) {
return SafeArea(
child: MultiProvider(
providers: [
ChangeNotifierProvider<EmailAuth>(create: (context) => EmailAuth()),
],
child: Scaffold(
resizeToAvoidBottomInset: true,
floatingActionButton: FloatingActionButton(.....
My Authentication function that is triggered when user logs-in (Firebase)
class EmailAuth extends ChangeNotifier {
final _auth = FirebaseAuth.instance;
final dbRef = FirebaseFirestore.instance.collection("users");
String userid;
Future signIn({String email, String password}) async {
final currentUser = await _auth.signInWithEmailAndPassword(
email: email, password: password);
if (currentUser != null) {
userid = _auth.currentUser.uid;
dbRef.doc(_auth.currentUser.uid).update({
"lastLogin": DateTime.now(),
});
} else {
print("something didn't work");
}
print(userid);
notifyListeners();
return userid;
}
}
This is how my Consumer is setup in the HomePage - AppBar
title: Consumer<EmailAuth>(
builder: (context, data, child) => Text(
"${data.userid}",
style: TextStyle(color: Colors.indigoAccent),
),
),
But the output on AppBar is NULL. What am I doing wrong?!
I have been using this as reference for implementation:
https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd
Something similar was a known error in the older Provider Package. Please update to latest and check if the issue is still there. However,
This is how a MultiProvider should look like:
#override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),
ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),
],
And should be consumed like this
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}

Flutter Test Mock GraphQL Mutation result

I'm trying to create widget tests for a flutter application using GraphQL.
What I want to do is to test the behaviour of the app which depends on the result of a GraphQL Mutation on a user action.
This is a very simple example of the app:
class FirstScreen extends StatelessWidget {
#override
Widget return Container(
child: Mutation(
options: myMutationOptions,
onCompleted: (dynamic result) {
final bool myBool = result['bool'] as bool;
if (myBool) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondScreen()));
} else {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ThirdScreen()));
}
},
builder: (RunMutation runMutation, QueryResult queryResult) {
return FlatButton(
child: Text('Button'),
onPressed: () async {
await runMutation(myParameters).networkResult;
},
);
},
),
);
}
What I would like to do is to mock the result of the mutation so in my widget tests, I can test that the button redirects to the SecondScreen or ThirdScreen depending of the result myBool.
How can I do that ?
I finally managed to successfully mock a GraphQL Mutation. Here is how I did it, it is inspired from #Gpack's comment but I had to add some modifications and details to it.
To make it easy to use I created a wrapper widget GraphQLMutationMocker :
class MockClient extends Mock implements Client {
MockClient({
this.mockedResult,
this.mockedStatus = 200,
});
final Map<String, dynamic> mockedResult;
final int mockedStatus;
#override
Future<StreamedResponse> send(BaseRequest request) {
return Future<StreamedResponse>.value(
StreamedResponse(
Stream.value(utf8.encode(jsonEncode(mockedResult))),
mockedStatus,
),
);
}
}
class GraphQLMutationMocker extends StatelessWidget {
const GraphQLMutationMocker({
#required this.child,
this.mockedResult = const {},
this.mockedStatus = 200,
this.url = 'http://url',
this.storagePrefix = 'test',
});
final Widget child;
final Map<String, dynamic> mockedResult;
final int mockedStatus;
final String url;
final String storagePrefix;
#override
Widget build(BuildContext context) {
final mockClient = MockClient(
mockedResult: mockedResult,
mockedStatus: mockedStatus,
);
final httpLink = HttpLink(
uri: url,
httpClient: mockClient,
);
final graphQLClient = ValueNotifier(
GraphQLClient(
cache: InMemoryCache(storagePrefix: storagePrefix),
link: httpLink,
),
);
return GraphQLProvider(
client: graphQLClient,
child: child,
);
}
}
Then it was pretty easy to write the tests
group('Test mutation', () {
testWidgets('It should redirect to SecondScreen', (WidgetTester tester) async {
await tester.pumpWidget(GraphQLMutationMocker(
mockedResult: <String, dynamic>{
'data': {
'bool': true,
},
},
child: FirstScreen(),
));
// Click on button
await tester.tap(find.text('Button'));
await tester.pumpAndSettle();
// Check I'm on the right screen
expect(find.byType(SecondScreen), findsOneWidget);
expect(find.byType(ThirdScreen), findsNothing);
});
testWidgets('It should redirect to ThirdScreen', (WidgetTester tester) async {
await tester.pumpWidget(GraphQLMutationMocker(
mockedResult: <String, dynamic>{
'data': {
'bool': false,
},
},
child: FirstScreen(),
));
// Click on button
await tester.tap(find.text('Button'));
await tester.pumpAndSettle();
// Check I'm on the right screen
expect(find.byType(SecondScreen), findsNothing);
expect(find.byType(ThirdScreen), findsOneWidget);
});
})
Create a mock of http.Client like in the flutter docs
In your test, wrap your FirstScreen in a GraphqlProvider like so:
class MockHttpClient extends Mock implements Client {}
group('Test mutation', () {
MockHttpClient mockHttpClient;
HttpLink httpLink;
ValueNotifier<GraphQLClient> client;
setUp(() async {
mockHttpClient = MockHttpClient();
httpLink = HttpLink(
uri: 'https://unused/graphql',
httpClient: mockHttpClient,
);
client = ValueNotifier(
GraphQLClient(
cache: InMemoryCache(storagePrefix: 'test'),
link: httpLink,
),
);
});
testWidgets('redirects to SecondScreen', (WidgetTester tester) async {
when(client.send(captureAny)).thenAnswer(/* ... */);
await tester.pumpWidget(GraphQLProvider(
client: client,
child: FirstScreen(),
));
// Click on button
verify(mockHttpClient.send(any)).called(1);
// etc.
});
})

Not getting provider data when navigating in flutter

I know flutter provider is scoped. So I declared providers (those will be needed everywhere) top of MaterialApp. In a screen am chaning a provider value and navigating to another screen. In that screen am not getting the data. Need suggestions and guide where I have done the mistake
main.dart
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<UserAuthViewModel>(
create: (context) => sl<UserAuthViewModel>()),
ChangeNotifierProvider<UserProfileViewModel>(
create: (context) => sl<UserProfileViewModel>()),
ChangeNotifierProvider<BottomNavViewModel>(
create: (context) => sl<BottomNavViewModel>()),
],
child: MaterialApp(
title: "Footsapp",
theme: ThemeData(fontFamily: 'Montserrat'),
debugShowCheckedModeBanner: false,
home: isFirstLaunch == true ? OnBoarding() : SplashView(),
routes: {
//onboarding
SplashView.SCREEN_ID: (context) => SplashView(),
//bottom nav pages
BottomNavContainer.SCREEN_ID: (context) => BottomNavContainer(),
},
),
),
);
splash screen where getting some info from api call
class _SplashViewState extends State<SplashView>
with TokenProvider, AfterLayoutMixin<SplashView> {
final userProfileViewModel = sl<UserProfileViewModel>();
final prefUtil = sl<SharedPrefUtil>();
#override
void afterFirstLayout(BuildContext context) {
Future.delayed(Duration(seconds: 1), () async {
bool isLoggedIn = prefUtil.readBool(IS_LOGGED_IN) ?? false;
bool initialProfileUpdated =
prefUtil.readBool(INITIAL_PROFILE_UPDATED) ?? false;
isLoggedIn == true
? getProfileInfo()
: await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => SocialLoginRegScreen(),
),
(route) => false);
});
}
#override
void initState() {
super.initState();
}
void getProfileInfo() async {
final userProfileResponse = await userProfileViewModel.getUserProfileData();
if (userProfileResponse.success == true) {
print('In splash: ${userProfileViewModel.userProfile.toString()}');
//from log
In splash: {firstName: Ashif, lastName: 123, birthday: 1990-02-03, email:
ashif123#gmail.com, preferredFoot: Left, preferredPosition: Midfielder,
numberOfTimesPlayedWeekly: 4}
Future.delayed(Duration(milliseconds: 500), () async {
await Navigator.pushReplacementNamed(
context,
BottomNavContainer.SCREEN_ID,
);
});
}
}
provider model class
class UserProfileViewModel extends BaseViewModel {
final _profileManageRepo = sl<ProfileManageRepo>();
Profile userProfile = Profile.initial();
Future<UserDataResponse> getUserProfileData() async {
final _userDataResponse = await _profileManageRepo.getUserProfileInfo();
if (_userDataResponse.success == true) {
userProfile = _userDataResponse.profile;
} else {
setState(ViewState.Error);
errorMessage = 'Please try again!';
}
return _userDataResponse;
}
Am trying to get the provider data (user profile) from Profile Screen. But it always get initial value.
class _UserProfileScreenState extends State<UserProfileScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Consumer<UserProfileViewModel>(
builder: (context, viewmodel, child) {
print('In profile: ${viewmodel.userProfile.toString()}');
//but here
In profile: {firstName: , lastName: , birthday: , email: , preferredFoot:
Left, preferredPosition: , numberOfTimesPlayedWeekly: 1(default value)}
return Container(
child: ProfileCardWidget(
profile: viewmodel.userProfile,
),
);
},
);
}
}