From 1bb9ebd07969c71f37b857c4b8d068c9ce4532bf Mon Sep 17 00:00:00 2001 From: aashish-g03 Date: Thu, 17 Oct 2024 11:41:42 +0530 Subject: [PATCH] feat: add tests for request caching service (#280) --- .../services/request_cache_service.dart | 6 +- pubspec.yaml | 1 + .../services/request_cache_service_test.dart | 248 ++++++++++++++++++ 3 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 test/shared/services/request_cache_service_test.dart diff --git a/lib/shared/services/request_cache_service.dart b/lib/shared/services/request_cache_service.dart index f7d80cc2..357f9193 100644 --- a/lib/shared/services/request_cache_service.dart +++ b/lib/shared/services/request_cache_service.dart @@ -10,13 +10,15 @@ class RequestCacheService { final Map Function(T) toJson; final Duration cacheDuration; final String networkCacheKey; + final http.Client httpClient; RequestCacheService({ required this.fromJson, required this.toJson, this.cacheDuration = const Duration(minutes: 1), this.networkCacheKey = 'network_cache', - }); + http.Client? httpClient, + }) : httpClient = httpClient ?? http.Client(); /// Fetches data from the cache and API. Stream fetchData(String url) async* { @@ -48,7 +50,7 @@ class RequestCacheService { // Fetch data from the network try { - final response = await http.get(Uri.parse(url)); + final response = await httpClient.get(Uri.parse(url)); if (response.statusCode == 200) { final dataMap = jsonDecode(response.body) as Map; diff --git a/pubspec.yaml b/pubspec.yaml index d52538d1..fac9da7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,7 @@ dependencies: url_launcher: ^6.2.5 webview_flutter: ^4.4.2 web5: ^0.4.0 + hive_test: ^1.0.1 dev_dependencies: flutter_test: diff --git a/test/shared/services/request_cache_service_test.dart b/test/shared/services/request_cache_service_test.dart new file mode 100644 index 00000000..66e31e32 --- /dev/null +++ b/test/shared/services/request_cache_service_test.dart @@ -0,0 +1,248 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:didpay/shared/services/request_cache_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:hive_test/hive_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; + +class TestData { + final int id; + final String name; + + TestData({required this.id, required this.name}); + + factory TestData.fromJson(Map json) { + return TestData( + id: json['id'] as int, + name: json['name'] as String, + ); + } + + Map toJson() => { + 'id': id, + 'name': name, + }; +} + +void main() { + // Initialize in-memory Hive storage before tests + setUp(() async { + await setUpTestHive(); + }); + + // Clean up after tests + tearDown(() async { + await tearDownTestHive(); + }); + + group('RequestCacheService Tests', () { + test('fetchData emits data from network when no cache exists', () async { + // Arrange + const testUrl = 'https://example.com/data'; + final mockResponseData = {'id': 1, 'name': 'Test Item'}; + final mockClient = MockClient((request) async { + if (request.url.toString() == testUrl) { + return http.Response(jsonEncode(mockResponseData), 200); + } + return http.Response('Not Found', 404); + }); + + final service = RequestCacheService( + fromJson: TestData.fromJson, + toJson: (data) => data.toJson(), + httpClient: mockClient, + ); + + final dataStream = service.fetchData(testUrl); + + await expectLater( + dataStream, + emits(predicate((data) => + data.id == mockResponseData['id'] && + data.name == mockResponseData['name'])), + ); + }); + + test( + 'fetchData emits cached data when cache is valid and data is unchanged', + () async { + // Arrange + const testUrl = 'https://example.com/data'; + final mockCachedData = {'id': 1, 'name': 'Cached Item'}; + final now = DateTime.now(); + + // Pre-populate the Hive box with cached data + final box = await Hive.openBox('network_cache'); + final cacheKey = testUrl.hashCode.toString(); + await box.put(cacheKey, { + 'data': mockCachedData, + 'timestamp': now.toIso8601String(), + }); + + // Mock HTTP client returns the same data as cached + final mockClient = MockClient((request) async { + return http.Response(jsonEncode(mockCachedData), 200); + }); + + final service = RequestCacheService( + fromJson: TestData.fromJson, + toJson: (data) => data.toJson(), + httpClient: mockClient, + ); + + final dataStream = service.fetchData(testUrl); + + await expectLater( + dataStream, + emits(predicate((data) => + data.id == mockCachedData['id'] && + data.name == mockCachedData['name'])), + ); + }); + + test( + 'fetchData emits new data when network data is different from cached data', + () async { + // Arrange + const testUrl = 'https://example.com/data'; + final mockCachedData = {'id': 1, 'name': 'Old Item'}; + final mockResponseData = {'id': 1, 'name': 'Updated Item'}; + final now = DateTime.now(); + + // Pre-populate the Hive box with old cached data + final box = await Hive.openBox('network_cache'); + final cacheKey = testUrl.hashCode.toString(); + await box.put(cacheKey, { + 'data': mockCachedData, + 'timestamp': now.toIso8601String(), + }); + + // Mock HTTP client returns updated data + final mockClient = MockClient((request) async { + return http.Response(jsonEncode(mockResponseData), 200); + }); + + final service = RequestCacheService( + fromJson: TestData.fromJson, + toJson: (data) => data.toJson(), + httpClient: mockClient, + ); + + final dataStream = service.fetchData(testUrl); + + await expectLater( + dataStream, + emitsInOrder([ + predicate((data) => + data.id == mockCachedData['id'] && + data.name == mockCachedData['name']), + predicate((data) => + data.id == mockResponseData['id'] && + data.name == mockResponseData['name']), + ]), + ); + }); + + test('fetchData throws error when no cache exists and network fails', + () async { + // Arrange + const testUrl = 'https://example.com/data'; + + // Mock HTTP client returns an error + final mockClient = MockClient((request) async { + return http.Response('Server Error', 500); + }); + + final service = RequestCacheService( + fromJson: TestData.fromJson, + toJson: (data) => data.toJson(), + httpClient: mockClient, + ); + + expect( + service.fetchData(testUrl), + emitsError(isA()), + ); + }); + + test('fetchData emits cached data when network fails', () async { + // Arrange + const testUrl = 'https://example.com/data'; + final mockCachedData = {'id': 1, 'name': 'Cached Item'}; + final now = DateTime.now(); + + // Pre-populate the Hive box with cached data + final box = await Hive.openBox('network_cache'); + final cacheKey = testUrl.hashCode.toString(); + await box.put(cacheKey, { + 'data': mockCachedData, + 'timestamp': now.toIso8601String(), + }); + + // Mock HTTP client returns an error + final mockClient = MockClient((request) async { + return http.Response('Server Error', 500); + }); + + final service = RequestCacheService( + fromJson: TestData.fromJson, + toJson: (data) => data.toJson(), + httpClient: mockClient, + ); + + final dataStream = service.fetchData(testUrl); + + await expectLater( + dataStream, + emits(predicate((data) => + data.id == mockCachedData['id'] && + data.name == mockCachedData['name'])), + ); + }); + + test('fetchData fetches new data when cache is expired', () async { + // Arrange + const testUrl = 'https://example.com/data'; + final mockCachedData = {'id': 1, 'name': 'Old Item'}; + final mockResponseData = {'id': 1, 'name': 'New Item'}; + final expiredTime = DateTime.now().subtract(Duration(minutes: 10)); + + // Pre-populate the Hive box with expired cached data + final box = await Hive.openBox('network_cache'); + final cacheKey = testUrl.hashCode.toString(); + await box.put(cacheKey, { + 'data': mockCachedData, + 'timestamp': expiredTime.toIso8601String(), + }); + + // Mock HTTP client returns new data + final mockClient = MockClient((request) async { + return http.Response(jsonEncode(mockResponseData), 200); + }); + + final service = RequestCacheService( + fromJson: TestData.fromJson, + toJson: (data) => data.toJson(), + httpClient: mockClient, + cacheDuration: Duration(minutes: 1), // Set cache duration for the test + ); + + final dataStream = service.fetchData(testUrl); + + await expectLater( + dataStream, + emitsInOrder([ + predicate((data) => + data.id == mockCachedData['id'] && + data.name == mockCachedData['name']), + predicate((data) => + data.id == mockResponseData['id'] && + data.name == mockResponseData['name']), + ]), + ); + }); + }); +}