felangel / mocktail

A mock library for Dart inspired by mockito
https://pub.dev/packages/mocktail
MIT License
626 stars 81 forks source link

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

Open DiegoVega19 opened 2 years ago

DiegoVega19 commented 2 years ago

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 commented 1 year ago

Hey @DiegoVega19, please format your code first and insert the whole error if you can. 🙏 https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax

tattivitorino commented 8 months ago

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 commented 8 months ago

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 commented 7 months ago

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!