import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lunchpick/domain/entities/restaurant.dart'; import 'package:lunchpick/domain/repositories/restaurant_repository.dart'; import 'package:lunchpick/presentation/providers/restaurant_provider.dart'; import 'package:lunchpick/presentation/providers/di_providers.dart'; import 'package:mockito/mockito.dart'; import 'package:mockito/annotations.dart'; @GenerateMocks([RestaurantRepository]) import 'restaurant_provider_test.mocks.dart'; void main() { group('RestaurantProvider Tests', () { late ProviderContainer container; late MockRestaurantRepository mockRepository; setUp(() { mockRepository = MockRestaurantRepository(); container = ProviderContainer( overrides: [ restaurantRepositoryProvider.overrideWithValue(mockRepository), ], ); }); tearDown(() { container.dispose(); }); test('restaurantListProvider returns stream of restaurants', () async { // Arrange final restaurants = [ Restaurant( id: '1', name: '테스트 맛집', category: 'korean', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ]; when( mockRepository.watchRestaurants(), ).thenAnswer((_) => Stream.value(restaurants)); // Act final result = container.read(restaurantListProvider); // Assert expect(result, isA>>()); }); test('searchRestaurantsProvider filters restaurants by query', () async { // Arrange final restaurants = [ Restaurant( id: '1', name: '김치찌개', category: 'korean', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), Restaurant( id: '2', name: '된장찌개', category: 'korean', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ]; when( mockRepository.searchRestaurants('김치'), ).thenAnswer((_) async => [restaurants[0]]); // Act final result = await container.read( searchRestaurantsProvider('김치').future, ); // Assert expect(result.length, 1); expect(result.first.name, '김치찌개'); }); test('selectedCategoryProvider updates category filter', () { // Act container.read(selectedCategoryProvider.notifier).state = '한식'; // Assert expect(container.read(selectedCategoryProvider), '한식'); }); test('restaurantNotifier adds new restaurant', () async { // Arrange when(mockRepository.addRestaurant(any)).thenAnswer((_) async {}); // Act final notifier = container.read(restaurantNotifierProvider.notifier); await notifier.addRestaurant( name: '새 맛집', category: 'korean', subCategory: '한식', roadAddress: '서울시 강남구', jibunAddress: '서울시 강남구', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, ); // Assert verify(mockRepository.addRestaurant(any)).called(1); }); test('restaurantNotifier updates existing restaurant', () async { // Arrange final restaurant = Restaurant( id: '1', name: '수정할 맛집', category: 'korean', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); when(mockRepository.updateRestaurant(any)).thenAnswer((_) async {}); // Act final notifier = container.read(restaurantNotifierProvider.notifier); await notifier.updateRestaurant(restaurant); // Assert verify(mockRepository.updateRestaurant(any)).called(1); }); test('restaurantNotifier deletes restaurant', () async { // Arrange when(mockRepository.deleteRestaurant('1')).thenAnswer((_) async {}); // Act final notifier = container.read(restaurantNotifierProvider.notifier); await notifier.deleteRestaurant('1'); // Assert verify(mockRepository.deleteRestaurant('1')).called(1); }); test( 'filteredRestaurantsProvider filters by search and category', () async { // Arrange final restaurants = [ Restaurant( id: '1', name: '김치찌개', category: '한식', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), Restaurant( id: '2', name: '스시', category: '일식', subCategory: '일식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5, longitude: 127.0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ]; when( mockRepository.watchRestaurants(), ).thenAnswer((_) => Stream.value(restaurants)); // Act - 카테고리 필터 설정 container.read(selectedCategoryProvider.notifier).state = '한식'; container.read(searchQueryProvider.notifier).state = '김치'; final filtered = await container.read( filteredRestaurantsProvider.future, ); // Assert expect(filtered.length, 1); expect(filtered.first.name, '김치찌개'); }, ); test( 'restaurantsWithinDistanceProvider returns nearby restaurants', () async { // Arrange final nearbyRestaurants = [ Restaurant( id: '1', name: '가까운 맛집', category: 'korean', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', latitude: 37.5665, longitude: 126.9780, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ]; when( mockRepository.getRestaurantsWithinDistance( userLatitude: 37.5665, userLongitude: 126.9780, maxDistanceInMeters: 1000, ), ).thenAnswer((_) async => nearbyRestaurants); // Act final result = await container.read( restaurantsWithinDistanceProvider(( latitude: 37.5665, longitude: 126.9780, maxDistance: 1000, )).future, ); // Assert expect(result.length, 1); expect(result.first.name, '가까운 맛집'); }, ); test('categoriesProvider emits sorted unique categories', () async { // Arrange final restaurants = [ Restaurant( id: '1', name: '카테고리1', category: '양식', subCategory: '파스타', roadAddress: '서울시', jibunAddress: '서울시', latitude: 0, longitude: 0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), Restaurant( id: '2', name: '카테고리2', category: '한식', subCategory: '찌개', roadAddress: '서울시', jibunAddress: '서울시', latitude: 0, longitude: 0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), Restaurant( id: '3', name: '카테고리3', category: '한식', subCategory: '비빔밥', roadAddress: '서울시', jibunAddress: '서울시', latitude: 0, longitude: 0, source: DataSource.USER_INPUT, createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ]; when( mockRepository.watchRestaurants(), ).thenAnswer((_) => Stream.value(restaurants)); final categoriesContainer = ProviderContainer( overrides: [ restaurantListProvider.overrideWith( (ref) => Stream.value(restaurants), ), ], ); final categories = await categoriesContainer .read(categoriesProvider.stream) .first; expect(categories, ['양식', '한식']); categoriesContainer.dispose(); }); }); }