как правильно тестировать Riverpod с помощью mockito

как правильно протестировать Riverpod с помощью mockito?

запуск кода выше,

      
/// ### edited snippets from production side ###
/// not important, skip to the TEST below!

/// this seems meaningless just because it is out of context
mixin FutureDelegate<T> {
  Future<T> call();
}

/// delegate implementation

import '../../shared/delegate/future_delegate.dart';

const k_STRING_DELEGATE = StringDelegate();

class StringDelegate implements FutureDelegate<String> {
  const StringDelegate();
  @override
  Future<String> call() async {
   /// ... returns a string at some point, not important now
  }
}



/// the future provider
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '<somewhere>/delegate.dart'; /// the code above

final stringProvider = FutureProvider<String>((ref) => k_STRING_DELEGATE());

/// ### edited snippets from TEST side ###


/// mocking the delegate
import 'package:mockito/mockito.dart';
import '<see above>/future_delegate.dart';

class MockDelegate extends Mock implements FutureDelegate<String> {}


/// actual test 
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/all.dart';
import 'package:mockito/mockito.dart';
import '<somewhere in my project>/provider.dart';
import '../../domain/<somewhere>/mock_delegate.dart'; // <= the code above

void main() {
  group('`stringProvider`', () {
    final _delegate = MockDelegate();
    test('WHEN `delegate` throws THEN `provider`return exception',
        () async {
      when(_delegate.call()).thenAnswer((_) async {
        await Future.delayed(const Duration(seconds: 1));
        throw 'ops';
      });

      final container = ProviderContainer(
        overrides: [
          stringProvider
              .overrideWithProvider(FutureProvider((ref) => _delegate()))
        ],
      );
      expect(
        container.read(stringProvider),
        const AsyncValue<String>.loading(),
      );
      await Future<void>.value();
      expect(container.read(stringProvider).data.value, [isA<Exception>()]);
    });
  });
}

запуск теста возвращает

      NoSuchMethodError: The getter 'value' was called on null.
  Receiver: null
  Tried calling: value
  dart:core                                Object.noSuchMethod
  src/logic/path/provider_test.dart 28:48  main.<fn>.<fn>

Я новичок в riverpod, очевидно , я что - то не хватает , я пытался следовать этим

3 ответа

Я обнаружил, что у меня были дополнительные ошибки, особенно при использовании StateNotifierProvider. Хитрость заключалась в том, чтобы не только переопределить StateNotifierProvider, но и его state свойство (которое является объектом StateNotifierStateProvider).

      class SomeState {
  final bool didTheThing;
  SomeState({this.didTheThing = false});
}

class SomeStateNotifier extends StateNotifier<SomeState> {
  SomeStateNotifier() : super(SomeState());

  bool doSomething() {
    state = SomeState(didTheThing: true);
    return true;
  }
}

final someStateProvider = StateNotifierProvider<SomeStateNotifier>((ref) {
  return SomeStateNotifier();
});

class MockStateNotifier extends Mock implements SomeStateNotifier {}

void main() {
  final mockStateNotifier = SomeStateNotifier();
  when(mockStateNotifier.doSomething()).thenReturn(true);

  final dummyState = SomeState(didTheThing: true); // This could also be mocked

  ProviderScope(
    overrides: [
      someStateProvider.overrideWithValue(mockStateProvider), // This covers usages like "useProvider(someStateProvider)"
      someStateProvider.state.overrideWithValue(dummyState),  // This covers usages like "useProvider(someStateProvider.state)"
    ],
    child: MaterialApp(...),
  );
}

Также рассмотрите возможность издевательства над различными поставщиками с помощью Override в вашем ProviderScope верхнего уровня. Это то, что переопределение может делать хорошо.

В вашем коде 2 ошибки

Вы пытаетесь проверить ошибку выброса, поэтому вам следует использовать thenThrow вместо thenAnswer, но поскольку вы переопределяете метод смешивания, я бы рекомендовал вместо использования Mock использовать Fake (из той же библиотеки mockito), чтобы переопределить методы, а затем выбросить его, как хотите

      class MockDelegate extends Fake implements FutureDelegate<String> {

  @override
  Future<String> call() async {
    throw NullThrownError; //now you can throw whatever you want
  }
}

И вторая проблема (и та, о которой вас предупреждает ваш код) заключается в том, что вы намеренно бросаете, поэтому вместо этого вы должны ожидать AsyncError, поэтому вызывая container.read(stringProvider).data.value является ошибкой из-за чтения документации по riverpod:

При вызове данных:

Текущие данные или null, если загрузка / ошибка.

поэтому, если вы ожидаете ошибки (AsyncError), данные равны нулю, и из-за этого вызова data.value это то же самое, что писать null.value какая ошибка вы испытываете

Вот код, который вы можете попробовать:

      class MockDelegate extends Fake implements FutureDelegate<String> {
  @override
  Future<String> call() async {
    throw NullThrownError;
  }
}

void main() {
  group('`stringProvider`', () {
    final _delegate = MockDelegate();
    test('WHEN `delegate` throws THEN `provider`return exception', () async {

      final container = ProviderContainer(
        overrides: [
          stringProvider
              .overrideWithProvider(FutureProvider((ref) => _delegate.call()))
        ],
      );

      expect(container.read(stringProvider), const AsyncValue<String>.loading());

      container.read(stringProvider).data.value;

      await Future<void>.value();
      expect(container.read(stringProvider), isA<AsyncError>()); // you're expecting to be of type AsyncError because you're throwing
    });
  });
}
Другие вопросы по тегам