diff --git a/flutter_secure_storage/test/flutter_secure_storage_mock.dart b/flutter_secure_storage/test/flutter_secure_storage_mock.dart new file mode 100644 index 00000000..05a3c034 --- /dev/null +++ b/flutter_secure_storage/test/flutter_secure_storage_mock.dart @@ -0,0 +1,56 @@ +import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockFlutterSecureStoragePlatform extends Mock + with MockPlatformInterfaceMixin + implements FlutterSecureStoragePlatform {} + +class ImplementsFlutterSecureStoragePlatform extends Mock + implements FlutterSecureStoragePlatform {} + +class ExtendsFlutterSecureStoragePlatform extends FlutterSecureStoragePlatform { + @override + Future containsKey({ + required String key, + required Map options, + }) => + Future.value(true); + + @override + Future delete({ + required String key, + required Map options, + }) => + Future.value(); + + @override + Future deleteAll({required Map options}) => + Future.value(); + + @override + Future read({ + required String key, + required Map options, + }) => + Future.value(); + + @override + Future> readAll({required Map options}) => + Future.value({}); + + @override + Future write({ + required String key, + required String value, + required Map options, + }) => + Future.value(); + + // @override + // Future isCupertinoProtectedDataAvailable() => Future.value(true); + // + // @override + // Stream get onCupertinoProtectedDataAvailabilityChanged => + // Stream.value(true); +} diff --git a/flutter_secure_storage/test/flutter_secure_storage_test.dart b/flutter_secure_storage/test/flutter_secure_storage_test.dart index 3357ce7f..b1cce438 100644 --- a/flutter_secure_storage/test/flutter_secure_storage_test.dart +++ b/flutter_secure_storage/test/flutter_secure_storage_test.dart @@ -1,25 +1,169 @@ +import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -// ✅ Correct Mock Class Implementation -class MockFlutterSecureStoragePlatform extends Mock - with MockPlatformInterfaceMixin - implements FlutterSecureStoragePlatform {} +import 'flutter_secure_storage_mock.dart'; void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late FlutterSecureStorage storage; late MockFlutterSecureStoragePlatform mockPlatform; + const channel = MethodChannel('plugins.it_nomads.com/flutter_secure_storage'); + final methodStorage = MethodChannelFlutterSecureStorage(); + final log = []; + + Future? handler(MethodCall methodCall) async { + log.add(methodCall); + if (methodCall.method == 'containsKey') { + return true; + } else if (methodCall.method == 'isProtectedDataAvailable') { + return true; + } + return null; + } + setUp(() { mockPlatform = MockFlutterSecureStoragePlatform(); FlutterSecureStoragePlatform.instance = mockPlatform; storage = const FlutterSecureStorage(); + + // Ensure method channel mock is set up for the tests + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, handler); + + log.clear(); // Clear logs before each test + }); + + tearDown(() { + log.clear(); // Clear logs after each test + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); // Remove the mock handler + }); + + group('Method Channel Interaction Tests for FlutterSecureStorage', () { + test('read', () async { + const key = 'test_key'; + const options = {}; + await methodStorage.read(key: key, options: options); + + expect( + log, + [ + isMethodCall( + 'read', + arguments: { + 'key': key, + 'options': options, + }, + ), + ], + ); + }); + + test('write', () async { + const key = 'test_key'; + const options = {}; + await methodStorage.write(key: key, value: 'test', options: options); + + expect( + log, + [ + isMethodCall( + 'write', + arguments: { + 'key': key, + 'value': 'test', + 'options': options, + }, + ), + ], + ); + }); + + test('containsKey', () async { + const key = 'test_key'; + const options = {}; + await methodStorage.write(key: key, value: 'test', options: options); + + final result = + await methodStorage.containsKey(key: key, options: options); + + expect(result, true); + }); + + test('delete', () async { + const key = 'test_key'; + const options = {}; + await methodStorage.write(key: key, value: 'test', options: options); + await methodStorage.delete(key: key, options: options); + + expect( + log, + [ + isMethodCall( + 'write', + arguments: { + 'key': key, + 'value': 'test', + 'options': options, + }, + ), + isMethodCall( + 'delete', + arguments: { + 'key': key, + 'options': options, + }, + ), + ], + ); + }); + + test('deleteAll', () async { + const options = {}; + await methodStorage.deleteAll(options: options); + + expect( + log, + [ + isMethodCall( + 'deleteAll', + arguments: { + 'options': options, + }, + ), + ], + ); + }); + }); + + group('Platform-Specific Interface Tests', () { + test('Cannot be implemented with `implements`', () { + expect( + () { + FlutterSecureStoragePlatform.instance = + ImplementsFlutterSecureStoragePlatform(); + }, + throwsA(isInstanceOf()), + ); + }); + + test('Can be mocked with `implements`', () { + final mock = MockFlutterSecureStoragePlatform(); + FlutterSecureStoragePlatform.instance = mock; + }); + + test('Can be extended', () { + FlutterSecureStoragePlatform.instance = + ExtendsFlutterSecureStoragePlatform(); + }); }); - group('FlutterSecureStorage Tests', () { + group('FlutterSecureStorage Methods Invocation Tests', () { const testKey = 'testKey'; const testValue = 'testValue'; @@ -118,7 +262,7 @@ void main() { }); }); - group('AndroidOptions Tests', () { + group('AndroidOptions Configuration Tests', () { test('Default AndroidOptions should have correct default values', () { const options = AndroidOptions.defaultOptions; @@ -201,7 +345,7 @@ void main() { }); }); - group('WebOptions Tests', () { + group('WebOptions Configuration Tests', () { test('Default WebOptions should have correct default values', () { const options = WebOptions.defaultOptions; @@ -261,7 +405,7 @@ void main() { }); }); - group('WindowsOptions Tests', () { + group('WindowsOptions Configuration Tests', () { test('Default WindowsOptions should have correct default values', () { const options = WindowsOptions.defaultOptions; @@ -308,7 +452,7 @@ void main() { }); }); - group('IOSOptions Tests', () { + group('iOSOptions Configuration Tests', () { test('Default IOSOptions should have correct default values', () { const options = IOSOptions.defaultOptions; @@ -368,8 +512,8 @@ void main() { }); }); - group('MacOsOptions Tests', () { - test('Default MacOsOptions should have correct default values', () { + group('macOSOptions Configuration Tests', () { + test('Default macOSOptions should have correct default values', () { // Ignore for test // ignore: use_named_constants const options = MacOsOptions(); @@ -382,7 +526,7 @@ void main() { }); }); - test('MacOsOptions with custom values', () { + test('macOSOptions with custom values', () { const options = MacOsOptions( accountName: 'macAccount', groupId: 'group.mac.example', @@ -400,7 +544,7 @@ void main() { }); }); - test('MacOsOptions defaultOptions matches default constructor', () { + test('macOSOptions defaultOptions matches default constructor', () { const defaultOptions = MacOsOptions.defaultOptions; // Ignore for test // ignore: use_named_constants