feat: Introduce comprehensive unit tests for models and BLoCs using mockito and bloc_test, and refine TripBloc error handling.
Some checks failed
Deploy to Play Store / build_and_deploy (push) Has been cancelled
Some checks failed
Deploy to Play Store / build_and_deploy (push) Has been cancelled
This commit is contained in:
130
test/blocs/auth_bloc_test.dart
Normal file
130
test/blocs/auth_bloc_test.dart
Normal file
@@ -0,0 +1,130 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:travel_mate/blocs/auth/auth_bloc.dart';
|
||||
import 'package:travel_mate/blocs/auth/auth_event.dart';
|
||||
import 'package:travel_mate/blocs/auth/auth_state.dart';
|
||||
import 'package:travel_mate/repositories/auth_repository.dart';
|
||||
import 'package:travel_mate/models/user.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
|
||||
|
||||
import 'package:travel_mate/services/notification_service.dart';
|
||||
|
||||
import 'auth_bloc_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([AuthRepository, NotificationService])
|
||||
void main() {
|
||||
group('AuthBloc', () {
|
||||
late MockAuthRepository mockAuthRepository;
|
||||
late MockNotificationService mockNotificationService;
|
||||
late AuthBloc authBloc;
|
||||
|
||||
final user = User(
|
||||
id: '123',
|
||||
nom: 'Doe',
|
||||
prenom: 'John',
|
||||
email: 'test@example.com',
|
||||
platform: 'email',
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
mockAuthRepository = MockAuthRepository();
|
||||
mockNotificationService = MockNotificationService();
|
||||
authBloc = AuthBloc(
|
||||
authRepository: mockAuthRepository,
|
||||
notificationService: mockNotificationService,
|
||||
);
|
||||
|
||||
// Default stub for saveTokenToFirestore to avoid strict mock errors
|
||||
when(
|
||||
mockNotificationService.saveTokenToFirestore(any),
|
||||
).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
authBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is AuthUninitialized', () {
|
||||
expect(authBloc.state, AuthInitial());
|
||||
});
|
||||
|
||||
blocTest<AuthBloc, AuthState>(
|
||||
'emits [AuthAuthenticated] when AuthCheckRequested is added and user is logged in',
|
||||
build: () {
|
||||
when(
|
||||
mockAuthRepository.currentUser,
|
||||
).thenReturn(MockFirebaseUser(uid: '123', email: 'test@example.com'));
|
||||
when(
|
||||
mockAuthRepository.getUserFromFirestore('123'),
|
||||
).thenAnswer((_) async => user);
|
||||
return authBloc;
|
||||
},
|
||||
act: (bloc) => bloc.add(AuthCheckRequested()),
|
||||
expect: () => [AuthLoading(), AuthAuthenticated(user: user)],
|
||||
);
|
||||
|
||||
blocTest<AuthBloc, AuthState>(
|
||||
'emits [AuthUnauthenticated] when AuthCheckRequested is added and user is not logged in',
|
||||
build: () {
|
||||
when(mockAuthRepository.currentUser).thenReturn(null);
|
||||
return authBloc;
|
||||
},
|
||||
act: (bloc) => bloc.add(AuthCheckRequested()),
|
||||
expect: () => [AuthLoading(), AuthUnauthenticated()],
|
||||
);
|
||||
|
||||
blocTest<AuthBloc, AuthState>(
|
||||
'emits [AuthAuthenticated] when AuthSignInRequested is added',
|
||||
build: () {
|
||||
when(
|
||||
mockAuthRepository.signInWithEmailAndPassword(
|
||||
email: 'test@example.com',
|
||||
password: 'password',
|
||||
),
|
||||
).thenAnswer((_) async => user);
|
||||
return authBloc;
|
||||
},
|
||||
act: (bloc) => bloc.add(
|
||||
const AuthSignInRequested(
|
||||
email: 'test@example.com',
|
||||
password: 'password',
|
||||
),
|
||||
),
|
||||
expect: () => [AuthLoading(), AuthAuthenticated(user: user)],
|
||||
);
|
||||
|
||||
blocTest<AuthBloc, AuthState>(
|
||||
'emits [AuthUnauthenticated] when AuthSignOutRequested is added',
|
||||
build: () {
|
||||
when(mockAuthRepository.signOut()).thenAnswer((_) async {});
|
||||
return authBloc;
|
||||
},
|
||||
act: (bloc) => bloc.add(AuthSignOutRequested()),
|
||||
expect: () => [AuthUnauthenticated()],
|
||||
verify: (_) {
|
||||
verify(mockAuthRepository.signOut()).called(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Simple Mock for FirebaseUser since we can't easily mock the real one without more boilerplate
|
||||
// or using firebase_auth_mocks package which we didn't add.
|
||||
// However, AuthRepository.currentUser returns firebase_auth.User.
|
||||
// Attempting to mock it via extends might be tricky due to private constructors.
|
||||
// Let's rely on Mockito to generate a mock for firebase_auth.User if needed,
|
||||
// or adjusting the test to not depend on the return value of currentUser being a complex object
|
||||
// if the Bloc only checks for null.
|
||||
//
|
||||
// Looking at AuthBloc source (I haven't read it yet), it probably checks `authRepository.currentUser`.
|
||||
// I'll read AuthBloc code in the next step to be sure how to mock the return value.
|
||||
class MockFirebaseUser extends Mock implements firebase_auth.User {
|
||||
@override
|
||||
final String uid;
|
||||
@override
|
||||
final String? email;
|
||||
|
||||
MockFirebaseUser({required this.uid, this.email});
|
||||
}
|
||||
198
test/blocs/auth_bloc_test.mocks.dart
Normal file
198
test/blocs/auth_bloc_test.mocks.dart
Normal file
@@ -0,0 +1,198 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in travel_mate/test/blocs/auth_bloc_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
|
||||
import 'package:firebase_auth/firebase_auth.dart' as _i4;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:travel_mate/models/user.dart' as _i5;
|
||||
import 'package:travel_mate/repositories/auth_repository.dart' as _i2;
|
||||
import 'package:travel_mate/services/notification_service.dart' as _i6;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
/// A class which mocks [AuthRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockAuthRepository extends _i1.Mock implements _i2.AuthRepository {
|
||||
MockAuthRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i3.Stream<_i4.User?> get authStateChanges =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.getter(#authStateChanges),
|
||||
returnValue: _i3.Stream<_i4.User?>.empty(),
|
||||
)
|
||||
as _i3.Stream<_i4.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> signInWithEmailAndPassword({
|
||||
required String? email,
|
||||
required String? password,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signInWithEmailAndPassword, [], {
|
||||
#email: email,
|
||||
#password: password,
|
||||
}),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> signUpWithEmailAndPassword({
|
||||
required String? email,
|
||||
required String? password,
|
||||
required String? nom,
|
||||
required String? prenom,
|
||||
required String? phoneNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signUpWithEmailAndPassword, [], {
|
||||
#email: email,
|
||||
#password: password,
|
||||
#nom: nom,
|
||||
#prenom: prenom,
|
||||
#phoneNumber: phoneNumber,
|
||||
}),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> signUpWithGoogle(
|
||||
String? phoneNumber,
|
||||
String? name,
|
||||
String? firstname,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signUpWithGoogle, [
|
||||
phoneNumber,
|
||||
name,
|
||||
firstname,
|
||||
]),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> signInWithGoogle() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signInWithGoogle, []),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> signUpWithApple(
|
||||
String? phoneNumber,
|
||||
String? name,
|
||||
String? firstname,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signUpWithApple, [phoneNumber, name, firstname]),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> signInWithApple() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signInWithApple, []),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> signOut() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#signOut, []),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> resetPassword(String? email) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#resetPassword, [email]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i5.User?> getUserFromFirestore(String? uid) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getUserFromFirestore, [uid]),
|
||||
returnValue: _i3.Future<_i5.User?>.value(),
|
||||
)
|
||||
as _i3.Future<_i5.User?>);
|
||||
}
|
||||
|
||||
/// A class which mocks [NotificationService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockNotificationService extends _i1.Mock
|
||||
implements _i6.NotificationService {
|
||||
MockNotificationService() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i3.Future<void> initialize() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#initialize, []),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
void startListening() => super.noSuchMethod(
|
||||
Invocation.method(#startListening, []),
|
||||
returnValueForMissingStub: null,
|
||||
);
|
||||
|
||||
@override
|
||||
_i3.Future<void> handleInitialMessage() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#handleInitialMessage, []),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<String?> getFCMToken() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getFCMToken, []),
|
||||
returnValue: _i3.Future<String?>.value(),
|
||||
)
|
||||
as _i3.Future<String?>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> saveTokenToFirestore(String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#saveTokenToFirestore, [userId]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
}
|
||||
108
test/blocs/group_bloc_test.dart
Normal file
108
test/blocs/group_bloc_test.dart
Normal file
@@ -0,0 +1,108 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:travel_mate/blocs/group/group_bloc.dart';
|
||||
import 'package:travel_mate/blocs/group/group_event.dart';
|
||||
import 'package:travel_mate/blocs/group/group_state.dart';
|
||||
import 'package:travel_mate/repositories/group_repository.dart';
|
||||
import 'package:travel_mate/models/group.dart';
|
||||
import 'package:travel_mate/models/group_member.dart';
|
||||
|
||||
import 'group_bloc_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([GroupRepository])
|
||||
void main() {
|
||||
group('GroupBloc', () {
|
||||
late MockGroupRepository mockGroupRepository;
|
||||
late GroupBloc groupBloc;
|
||||
|
||||
final group = Group(
|
||||
id: 'group1',
|
||||
name: 'Test Group',
|
||||
tripId: 'trip1',
|
||||
createdBy: 'user1',
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
mockGroupRepository = MockGroupRepository();
|
||||
groupBloc = GroupBloc(mockGroupRepository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
groupBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is GroupInitial', () {
|
||||
expect(groupBloc.state, GroupInitial());
|
||||
});
|
||||
|
||||
// LoadGroupsByUserId - Stream test
|
||||
// Stream mocking is a bit verbose with Mockito. We simulate stream behavior.
|
||||
blocTest<GroupBloc, GroupState>(
|
||||
'emits [GroupLoading, GroupsLoaded] when LoadGroupsByUserId is added',
|
||||
setUp: () {
|
||||
when(
|
||||
mockGroupRepository.getGroupsByUserId('user1'),
|
||||
).thenAnswer((_) => Stream.value([group]));
|
||||
},
|
||||
build: () => groupBloc,
|
||||
act: (bloc) => bloc.add(LoadGroupsByUserId('user1')),
|
||||
expect: () => [
|
||||
GroupLoading(),
|
||||
GroupsLoaded([group]),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<GroupBloc, GroupState>(
|
||||
'emits [GroupLoading, GroupError] when LoadGroupsByUserId stream errors',
|
||||
setUp: () {
|
||||
when(
|
||||
mockGroupRepository.getGroupsByUserId('user1'),
|
||||
).thenAnswer((_) => Stream.error('Error loading groups'));
|
||||
},
|
||||
build: () => groupBloc,
|
||||
act: (bloc) => bloc.add(LoadGroupsByUserId('user1')),
|
||||
expect: () => [GroupLoading(), GroupError('Error loading groups')],
|
||||
);
|
||||
|
||||
// CreateGroup
|
||||
blocTest<GroupBloc, GroupState>(
|
||||
'emits [GroupLoading, GroupCreated, GroupOperationSuccess] when CreateGroup is added',
|
||||
setUp: () {
|
||||
when(
|
||||
mockGroupRepository.createGroupWithMembers(
|
||||
group: anyNamed('group'),
|
||||
members: anyNamed('members'),
|
||||
),
|
||||
).thenAnswer((_) async => 'group1');
|
||||
},
|
||||
build: () => groupBloc,
|
||||
act: (bloc) => bloc.add(CreateGroup(group)),
|
||||
expect: () => [
|
||||
GroupLoading(),
|
||||
GroupCreated(groupId: 'group1'),
|
||||
GroupOperationSuccess('Group created successfully'),
|
||||
],
|
||||
);
|
||||
|
||||
// AddMemberToGroup
|
||||
final member = GroupMember(
|
||||
userId: 'user2',
|
||||
firstName: 'Bob',
|
||||
role: 'member',
|
||||
joinedAt: DateTime.now(),
|
||||
);
|
||||
blocTest<GroupBloc, GroupState>(
|
||||
'emits [GroupOperationSuccess] when AddMemberToGroup is added',
|
||||
setUp: () {
|
||||
when(
|
||||
mockGroupRepository.addMember('group1', member),
|
||||
).thenAnswer((_) async {});
|
||||
},
|
||||
build: () => groupBloc,
|
||||
act: (bloc) => bloc.add(AddMemberToGroup('group1', member)),
|
||||
expect: () => [GroupOperationSuccess('Member added')],
|
||||
);
|
||||
});
|
||||
}
|
||||
135
test/blocs/group_bloc_test.mocks.dart
Normal file
135
test/blocs/group_bloc_test.mocks.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in travel_mate/test/blocs/group_bloc_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i6;
|
||||
import 'package:travel_mate/models/group.dart' as _i4;
|
||||
import 'package:travel_mate/models/group_member.dart' as _i5;
|
||||
import 'package:travel_mate/repositories/group_repository.dart' as _i2;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
/// A class which mocks [GroupRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockGroupRepository extends _i1.Mock implements _i2.GroupRepository {
|
||||
MockGroupRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i3.Future<String> createGroupWithMembers({
|
||||
required _i4.Group? group,
|
||||
required List<_i5.GroupMember>? members,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#createGroupWithMembers, [], {
|
||||
#group: group,
|
||||
#members: members,
|
||||
}),
|
||||
returnValue: _i3.Future<String>.value(
|
||||
_i6.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#createGroupWithMembers, [], {
|
||||
#group: group,
|
||||
#members: members,
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<String>);
|
||||
|
||||
@override
|
||||
_i3.Stream<List<_i4.Group>> getGroupsByUserId(String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getGroupsByUserId, [userId]),
|
||||
returnValue: _i3.Stream<List<_i4.Group>>.empty(),
|
||||
)
|
||||
as _i3.Stream<List<_i4.Group>>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i4.Group?> getGroupById(String? groupId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getGroupById, [groupId]),
|
||||
returnValue: _i3.Future<_i4.Group?>.value(),
|
||||
)
|
||||
as _i3.Future<_i4.Group?>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i4.Group?> getGroupByTripId(String? tripId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getGroupByTripId, [tripId]),
|
||||
returnValue: _i3.Future<_i4.Group?>.value(),
|
||||
)
|
||||
as _i3.Future<_i4.Group?>);
|
||||
|
||||
@override
|
||||
_i3.Future<List<_i5.GroupMember>> getGroupMembers(String? groupId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getGroupMembers, [groupId]),
|
||||
returnValue: _i3.Future<List<_i5.GroupMember>>.value(
|
||||
<_i5.GroupMember>[],
|
||||
),
|
||||
)
|
||||
as _i3.Future<List<_i5.GroupMember>>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> addMember(String? groupId, _i5.GroupMember? member) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#addMember, [groupId, member]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> removeMember(String? groupId, String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#removeMember, [groupId, userId]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> updateGroup(String? groupId, _i4.Group? group) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#updateGroup, [groupId, group]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> deleteGroup(String? tripId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteGroup, [tripId]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Stream<List<_i5.GroupMember>> watchGroupMembers(String? groupId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#watchGroupMembers, [groupId]),
|
||||
returnValue: _i3.Stream<List<_i5.GroupMember>>.empty(),
|
||||
)
|
||||
as _i3.Stream<List<_i5.GroupMember>>);
|
||||
}
|
||||
103
test/blocs/trip_bloc_test.dart
Normal file
103
test/blocs/trip_bloc_test.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:travel_mate/blocs/trip/trip_bloc.dart';
|
||||
import 'package:travel_mate/blocs/trip/trip_event.dart';
|
||||
import 'package:travel_mate/blocs/trip/trip_state.dart';
|
||||
import 'package:travel_mate/repositories/trip_repository.dart';
|
||||
import 'package:travel_mate/models/trip.dart';
|
||||
|
||||
import 'trip_bloc_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([TripRepository])
|
||||
void main() {
|
||||
group('TripBloc', () {
|
||||
late MockTripRepository mockTripRepository;
|
||||
late TripBloc tripBloc;
|
||||
|
||||
final trip = Trip(
|
||||
id: 'trip1',
|
||||
title: 'Summer Vacation',
|
||||
description: 'Trip to the beach',
|
||||
location: 'Miami',
|
||||
startDate: DateTime(2023, 6, 1),
|
||||
endDate: DateTime(2023, 6, 10),
|
||||
createdBy: 'user1',
|
||||
createdAt: DateTime(2023, 1, 1),
|
||||
updatedAt: DateTime(2023, 1, 2),
|
||||
participants: ['user1'],
|
||||
status: 'active',
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
mockTripRepository = MockTripRepository();
|
||||
tripBloc = TripBloc(mockTripRepository);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tripBloc.close();
|
||||
});
|
||||
|
||||
test('initial state is TripInitial', () {
|
||||
expect(tripBloc.state, TripInitial());
|
||||
});
|
||||
|
||||
// LoadTripsByUserId
|
||||
blocTest<TripBloc, TripState>(
|
||||
'emits [TripLoading, TripLoaded] when LoadTripsByUserId is added',
|
||||
setUp: () {
|
||||
when(
|
||||
mockTripRepository.getTripsByUserId('user1'),
|
||||
).thenAnswer((_) => Stream.value([trip]));
|
||||
},
|
||||
build: () => tripBloc,
|
||||
act: (bloc) => bloc.add(const LoadTripsByUserId(userId: 'user1')),
|
||||
expect: () => [
|
||||
TripLoading(),
|
||||
TripLoaded([trip]),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<TripBloc, TripState>(
|
||||
'emits [TripLoading, TripError] when stream error',
|
||||
setUp: () {
|
||||
when(
|
||||
mockTripRepository.getTripsByUserId('user1'),
|
||||
).thenAnswer((_) => Stream.error('Error loading trips'));
|
||||
},
|
||||
build: () => tripBloc,
|
||||
act: (bloc) => bloc.add(const LoadTripsByUserId(userId: 'user1')),
|
||||
expect: () => [
|
||||
TripLoading(),
|
||||
const TripError('Impossible de charger les voyages'),
|
||||
],
|
||||
);
|
||||
|
||||
// TripCreateRequested
|
||||
blocTest<TripBloc, TripState>(
|
||||
'emits [TripLoading, TripCreated] when TripCreateRequested is added',
|
||||
setUp: () {
|
||||
when(
|
||||
mockTripRepository.createTrip(any),
|
||||
).thenAnswer((_) async => 'trip1');
|
||||
},
|
||||
build: () => tripBloc,
|
||||
act: (bloc) => bloc.add(TripCreateRequested(trip: trip)),
|
||||
// Note: TripBloc automatically refreshes list if _currentUserId is set.
|
||||
// Here we haven't loaded trips so _currentUserId is null.
|
||||
expect: () => [TripLoading(), const TripCreated(tripId: 'trip1')],
|
||||
);
|
||||
|
||||
// TripDeleteRequested
|
||||
blocTest<TripBloc, TripState>(
|
||||
'emits [TripOperationSuccess] when TripDeleteRequested is added',
|
||||
setUp: () {
|
||||
when(mockTripRepository.deleteTrip('trip1')).thenAnswer((_) async {});
|
||||
},
|
||||
build: () => tripBloc,
|
||||
act: (bloc) => bloc.add(const TripDeleteRequested(tripId: 'trip1')),
|
||||
expect: () => [const TripOperationSuccess('Trip deleted successfully')],
|
||||
);
|
||||
});
|
||||
}
|
||||
81
test/blocs/trip_bloc_test.mocks.dart
Normal file
81
test/blocs/trip_bloc_test.mocks.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in travel_mate/test/blocs/trip_bloc_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i3;
|
||||
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:mockito/src/dummies.dart' as _i5;
|
||||
import 'package:travel_mate/models/trip.dart' as _i4;
|
||||
import 'package:travel_mate/repositories/trip_repository.dart' as _i2;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
|
||||
/// A class which mocks [TripRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTripRepository extends _i1.Mock implements _i2.TripRepository {
|
||||
MockTripRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i3.Stream<List<_i4.Trip>> getTripsByUserId(String? userId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getTripsByUserId, [userId]),
|
||||
returnValue: _i3.Stream<List<_i4.Trip>>.empty(),
|
||||
)
|
||||
as _i3.Stream<List<_i4.Trip>>);
|
||||
|
||||
@override
|
||||
_i3.Future<String> createTrip(_i4.Trip? trip) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#createTrip, [trip]),
|
||||
returnValue: _i3.Future<String>.value(
|
||||
_i5.dummyValue<String>(
|
||||
this,
|
||||
Invocation.method(#createTrip, [trip]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i3.Future<String>);
|
||||
|
||||
@override
|
||||
_i3.Future<_i4.Trip?> getTripById(String? tripId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getTripById, [tripId]),
|
||||
returnValue: _i3.Future<_i4.Trip?>.value(),
|
||||
)
|
||||
as _i3.Future<_i4.Trip?>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> updateTrip(String? tripId, _i4.Trip? trip) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#updateTrip, [tripId, trip]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
|
||||
@override
|
||||
_i3.Future<void> deleteTrip(String? tripId) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteTrip, [tripId]),
|
||||
returnValue: _i3.Future<void>.value(),
|
||||
returnValueForMissingStub: _i3.Future<void>.value(),
|
||||
)
|
||||
as _i3.Future<void>);
|
||||
}
|
||||
Reference in New Issue
Block a user