From d05e378569c8cb507db2ae69ec27746806264c98 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Wed, 26 Nov 2025 19:16:27 +0900 Subject: [PATCH] test(app): add geocoding and restaurant card coverage --- .../remote/naver_search_service_test.dart | 21 +++++ .../providers/restaurant_provider_test.dart | 76 ++++++++++++++++++- test/widget/restaurant_card_test.dart | 65 ++++++++++++++++ 3 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 test/widget/restaurant_card_test.dart diff --git a/test/unit/data/datasources/remote/naver_search_service_test.dart b/test/unit/data/datasources/remote/naver_search_service_test.dart index ea30bd4..4bef1e1 100644 --- a/test/unit/data/datasources/remote/naver_search_service_test.dart +++ b/test/unit/data/datasources/remote/naver_search_service_test.dart @@ -1,3 +1,7 @@ +// ignore_for_file: depend_on_referenced_packages + +import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; import 'package:lunchpick/data/datasources/remote/naver_search_service.dart'; import 'package:lunchpick/data/datasources/remote/naver_map_parser.dart'; @@ -5,6 +9,15 @@ import 'package:lunchpick/data/api/naver_api_client.dart'; import 'package:lunchpick/data/api/naver/naver_local_search_api.dart'; import 'package:lunchpick/domain/entities/restaurant.dart'; import 'package:lunchpick/core/errors/network_exceptions.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +class _FakePathProvider extends PathProviderPlatform { + @override + Future getTemporaryPath() async { + final tempDir = await Directory.systemTemp.createTemp('naver_search_test_'); + return tempDir.path; + } +} // Mock 클래스들 class MockNaverApiClient extends NaverApiClient { @@ -75,6 +88,9 @@ class MockNaverApiClient extends NaverApiClient { void dispose() { disposeCalled = true; } + + @override + Future resolveShortUrl(String shortUrl) async => shortUrl; } class MockNaverMapParser extends NaverMapParser { @@ -123,6 +139,11 @@ void main() { late MockNaverApiClient mockApiClient; late MockNaverMapParser mockMapParser; + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + PathProviderPlatform.instance = _FakePathProvider(); + }); + setUp(() { mockApiClient = MockNaverApiClient(); mockMapParser = MockNaverMapParser(); diff --git a/test/unit/presentation/providers/restaurant_provider_test.dart b/test/unit/presentation/providers/restaurant_provider_test.dart index b223f76..732017b 100644 --- a/test/unit/presentation/providers/restaurant_provider_test.dart +++ b/test/unit/presentation/providers/restaurant_provider_test.dart @@ -177,7 +177,7 @@ void main() { Restaurant( id: '1', name: '김치찌개', - category: 'korean', + category: '한식', subCategory: '한식', roadAddress: '서울시', jibunAddress: '서울시', @@ -190,7 +190,7 @@ void main() { Restaurant( id: '2', name: '스시', - category: 'japanese', + category: '일식', subCategory: '일식', roadAddress: '서울시', jibunAddress: '서울시', @@ -208,10 +208,14 @@ void main() { // Act - 카테고리 필터 설정 container.read(selectedCategoryProvider.notifier).state = '한식'; + container.read(searchQueryProvider.notifier).state = '김치'; + final filtered = await container.read( + filteredRestaurantsProvider.future, + ); // Assert - // filteredRestaurantsProvider는 StreamProvider이므로 실제 테스트에서는 - // 비동기 처리가 필요함 + expect(filtered.length, 1); + expect(filtered.first.name, '김치찌개'); }, ); @@ -257,5 +261,69 @@ void main() { 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(); + }); }); } diff --git a/test/widget/restaurant_card_test.dart b/test/widget/restaurant_card_test.dart new file mode 100644 index 0000000..7fd742d --- /dev/null +++ b/test/widget/restaurant_card_test.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lunchpick/domain/entities/restaurant.dart'; +import 'package:lunchpick/presentation/pages/restaurant_list/widgets/restaurant_card.dart'; +import 'package:lunchpick/presentation/providers/visit_provider.dart'; + +void main() { + Restaurant buildRestaurant({required bool needsAddressVerification}) { + return Restaurant( + id: '1', + name: '테스트 식당', + category: '한식', + subCategory: '찌개', + roadAddress: '서울시 중구', + jibunAddress: '서울시 중구', + latitude: 37.5665, + longitude: 126.9780, + source: DataSource.USER_INPUT, + createdAt: DateTime(2024, 1, 1), + updatedAt: DateTime(2024, 1, 1), + needsAddressVerification: needsAddressVerification, + ); + } + + ProviderScope wrapWithProviders(Widget child) { + return ProviderScope( + overrides: [ + lastVisitDateProvider.overrideWithProvider( + (id) => FutureProvider((ref) async => null), + ), + ], + child: MaterialApp(home: child), + ); + } + + testWidgets('주소 확인 배지가 표시되고 거리 뱃지가 노출된다', (tester) async { + final restaurant = buildRestaurant(needsAddressVerification: true); + + await tester.pumpWidget( + wrapWithProviders( + RestaurantCard(restaurant: restaurant, distanceKm: 2.3), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('주소확인'), findsOneWidget); + expect(find.text('2.0km 이상'), findsOneWidget); + }); + + testWidgets('주소 확인 플래그가 없으면 배지가 숨겨진다', (tester) async { + final restaurant = buildRestaurant(needsAddressVerification: false); + + await tester.pumpWidget( + wrapWithProviders( + RestaurantCard(restaurant: restaurant, distanceKm: null), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('주소확인'), findsNothing); + }); +}