Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flutter Secure Storage Mock Throws type 'Null' is not a subtype of type 'Future<String?>' #168

Open
DiegoVega19 opened this issue Nov 29, 2022 · 4 comments
Labels
question Further information is requested waiting for response Waiting for follow up

Comments

@DiegoVega19
Copy link

Hello, could someone suggest me an idea how to solve this?
When im trying to mock flutter secure storage i get this error " type 'Null' is not a subtype of type 'Future<String?> "

-This is my storage

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

abstract class IStorage {
Future saveData(String value, String key);

Future getId(String key);
}

class MyStorage implements IStorage {
MyStorage(this._storage);
final FlutterSecureStorage _storage;
@OverRide
Future getId(String key) async {
return await _storage.read(key: key) ?? 'No data founded';
}

@OverRide
Future saveData(String value, String key) async {
await _storage.write(key: key, value: value);
}
}

-My Service

import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:fixed/shared/storage.dart';
import 'package:flutter/cupertino.dart';

class ApiService {
final String _baseURL = 'https://jsonplaceholder.typicode.com/';
Dio _dio = Dio(BaseOptions());
final IStorage _storage;

ApiService(this._storage, {Dio? dio}) {
_dio = dio ?? Dio();
_dio.options.baseUrl = _baseURL;
}

Future get() async {
String id = await _storage.getId('id');
final response = await _dio.get('${_baseURL}posts/$id');
debugPrint(json.encode(response.data));
return json.encode(response.data);
}
}

My Test File

import 'package:dio/dio.dart';
import 'package:fixed/api/api_service.dart';
import 'package:fixed/shared/storage.dart';
import 'package:fixed/store/store1.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:mocktail/mocktail.dart';

class MockStorage extends Mock implements IStorage {}

class MockStorage2 extends Mock implements MyStorage {}

class MockFlutterSecureStorage extends Mock implements FlutterSecureStorage {}

void main() {
final dio = Dio();
final dioAdapter = DioAdapter(dio: dio, matcher: const UrlRequestMatcher());
late ApiService service;
late MockFlutterSecureStorage mockSecureStorage;

setUp(() {
mockStorage = MockStorage();
mockSecureStorage = MockFlutterSecureStorage();
dio.httpClientAdapter = dioAdapter;
service = ApiService(dio: dio, MyStorage(mockSecureStorage));
});

void mockData() {
dioAdapter.onGet('https://jsonplaceholder.typicode.com/posts/1', (request) {
return request.reply(200, {'data': 'sucessfull'});
});
}

void mockGetId() {
when(() => mockStorage.getId('id'))
.thenAnswer((invocation) => Future.value('1'));
}

group('Test Service', () {
test('Test endpoint', () async {
//IT THROW ERROR
when(() => mockSecureStorage.read(key: 'íd')).thenAnswer((_) async => '');
final data = await service.get();
expect(data, equals('{"data":"sucessfull"}'));
verify(() => mockSecureStorage.read(key: 'id')).called(1);
});

});
}

@tenhobi
Copy link

tenhobi commented Feb 16, 2023

@tattivitorino
Copy link

tattivitorino commented Mar 21, 2024

i know it's been a year but i have the same problem here and for the sake of brevity i'll show only the write function.. and the weird thing is that when i test the saveLocalData isolating the storage.write as you'll see below the test passes but inside my authenticateCrewMember it does not..
my AuthProvider uses the StorageService as a dependency and DI is made with get_it on app load

abstract class StorageService {

  Future<bool> write({
    required String key,
    required String value,
    bool deleteAll = false,
  });
}

class StorageServiceImpl implements StorageService {
  StorageServiceImpl(this._storage);

  final FlutterSecureStorage? _storage;

  @override
  Future<bool> write({
    required String key,
    required String value,
    bool deleteAll = false,
  }) async {
    try {
      await _storage?.write(
        key: key,
        value: value,
        aOptions: getStorageAndroidOptions(),
      );
      return true;
    } on PlatformException catch (e, s) {
      log('[PLATFORM-EXCEPTION] - STORAGE-SERVICE:WRITE: $e, $s');
      if (deleteAll) {
        await _storage?.deleteAll();
      }
      return false;
    } catch (e, s) {
      log('[ERROR] - STORAGE-SERVICE:WRITE: $e, $s');
      return false;
    }
  }
}

Auth Provider

class AuthProvider with ChangeNotifier {
  AuthProvider({
    required this.storageService,
    required this.onboardService,
  });
  final StorageService storageService;
  final OnboardService onboardService;

  CrewModel? _crew;
  CrewModel? get crew => _crew;

  bool get isAuth {
    return _crew != null && _crew!.qrcodePayload.isNotEmpty;
  }

  Future<bool> authenticateCrewMember({required String qrCodePayload}) async {
    log('AUTHENTICATING CREW MEMBER...');
    try {

      CrewModel? crew = await onboardService.authenticateCrewMember(
        qrCodePayload: qrCodePayload,
      );
      if (crew == null) {
        throw AppException('O QRCode apresentado não retornou dados.');
      }
      _crew = crew.copyWith(qrcodePayload: qrCodePayload);

      await saveLocalData(_crew!);

      log('[RESPONSE:PRD] - AUTH-CREW-MEMBER: _crew: $_crew');
      notifyListeners();

      return true;
    } catch (e, s) {
      log('[ERROR:PRD] - AUTH-CREW-MEMBER - $e, $s');
      throw AppException('$e');
    }
  }

  Future<void> saveLocalData(CrewModel crew) async {
    log('saving data');
    try {
      await storageService.write(
        key: STORAGE_CREW_AUTH_KEY,
        value: crew.toJson(),
      );
      log('data saved');
    } catch (e, s) {
      log('save error: $e, $s');
    }
  }
}

AuthProviderTest

class MockStorageService extends Mock implements StorageService {}
class MockOnboardService extends Mock implements OnboardService {}


void main() {
  late StorageService storageService;
  late OnboardService onboardService;

  late AuthProvider authProvider;

  setUp(() {
    storageService = MockStorageService();
    onboardService = MockOnboardService();

    authProvider = AuthProvider(
      storageService: storageService,
      onboardService: onboardService,
    );
  });

  tearDown(() {
    resetMocktailState();
  });  

  group('AuthProvider.saveLocalData -', () {
    *** this test passes ***

    test('should call saveLocalData and return void when successful', () async {
      final tModel = CrewModel.empty();
      when(() => storageService.write(
            key: STORAGE_CREW_AUTH_KEY,
            value: tModel.toJson(),
          )).thenAnswer((_) => Future.value(true));

      await authProvider.saveLocalData(tModel);
      verify(() => storageService.write(
            key: STORAGE_CREW_AUTH_KEY,
            value: tModel.toJson(),
          )).called(1);
    });
  });

  group('AuthProvider.authenticateCrewMember -', () {
    test(
        'should call authenticateCrewMember and return a CrewModel when successful',
        () async {
      const qrCodePayload = 'xxx';
      final tModel = CrewModel.empty();      

      when(() => onboardService.authenticateCrewMember(
              qrCodePayload: qrCodePayload))
          .thenAnswer((_) => Future.value(tModel));      

     *** this throws the error ***
      when(() => storageService.write(
            key: STORAGE_CREW_AUTH_KEY,
            value: tModel.toJson(),
          )).thenAnswer((_) => Future.value(true));

      final result = await authProvider.authenticateCrewMember(
          qrCodePayload: qrCodePayload);

      expect(result, true);
      expect(authProvider.crew, isNotNull);
      expect(authProvider.crew?.qrcodePayload, qrCodePayload);
      
      verify(() => onboardService.authenticateCrewMember(
          qrCodePayload: qrCodePayload)).called(1);

      verifyNoMoreInteractions(onboardService);

      *** this test does not pass because it is not called ***
      verify(() => storageService.write(
            key: STORAGE_CREW_AUTH_KEY,
            value: tModel.toJson(),
          )).called(1);
      verifyNoMoreInteractions(storageService);
    });
  });
}

Thats the error when I run debug in the test

type 'Null' is not a subtype of type 'Future<bool>', #0      MockStorageService.write (file:///Users/XXX/Dev/YYY/my-app/test/provider/auth.provider_test.dart:11:7)
      #1      AuthProvider.saveLocalData (package:my-app/provider/auth.provider.dart:148:28)
      #2      AuthProvider.authenticateCrewMember (package:my-app/provider/auth.provider.dart:60:13)
      <asynchronous suspension>
      #3      main.<anonymous closure>.<anonymous closure> (file:///Users/XXX/Dev/YYY/my-app/test/provider/auth.provider_test.dart:109:22)
      <asynchronous suspension>
      #4      Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:215:9)
      <asynchronous suspension>
      #5      Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:213:7)
      <asynchronous suspension>
      #6      Invoker._waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:258:9)
      <asynchronous suspension>

any help will be very much appreciated..
cheers

@tattivitorino
Copy link

forgot:

Flutter doctor

[✓] Flutter (Channel stable, 3.16.0, on macOS 13.2.1 22D68 darwin-arm64, locale en-BR)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.1)
[✓] VS Code (version 1.87.2)
[✓] Connected device (3 available)
[✓] Network resources

flutter_secure_storage: ^9.0.0
mocktail: ^1.0.3

@felangel
Copy link
Owner

If anyone is still having this issue can you please provide a link to a minimal reproduction sample? it's much easier for me to help if I'm able to reproduce the issue locally, thanks!

@felangel felangel added question Further information is requested waiting for response Waiting for follow up labels Apr 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested waiting for response Waiting for follow up
Projects
None yet
Development

No branches or pull requests

4 participants