Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what is the correct approach to test riverpod with mockito

what is the correct approach to test riverpod with mockito?

running the code above,


/// ### 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>()]);
    });
  });
}

running the test returns

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>

I'm new to riverpod, clearly I'm missing something I tried to follow this

like image 385
Francesco Iapicca Avatar asked Oct 30 '25 22:10

Francesco Iapicca


2 Answers

I found that I had some extra errors specifically when using StateNotifierProvider. The trick was to not only override the StateNotifierProvider, but also its state property (which is a StateNotifierStateProvider object).

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 = MockStateNotifier();
  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(...),
  );
}
like image 125
CaseyJ Avatar answered Nov 02 '25 13:11

CaseyJ


There are 2 errors in your code

You're trying to test a throw error, so you should use thenThrow instead of thenAnswer, but because you're overriding a mixing method I would recommend instead of using Mock use Fake (from the same mockito library) to override methods and then throw it as you want

class MockDelegate extends Fake implements FutureDelegate<String> {

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

And the second problem (and the one your code is warning you) is that you deliberately are throwing, so you should expect an AsyncError instead, so calling container.read(stringProvider).data.value is an error because reading the riverpod documentation:

When calling data:

The current data, or null if in loading/error.

so if you're expecting an error (AsyncError) data is null, and because of that calling data.value its the same as writing null.value which is the error you're experiencing

This is the code you could try:

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
    });
  });
}
like image 43
EdwynZN Avatar answered Nov 02 '25 13:11

EdwynZN



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!