riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

How to UnitTest a FutureProvider.autoDispose()?

Open myThorsten opened this issue 3 years ago • 2 comments

Describe what scenario you think is uncovered by the existing examples/articles I successfully tested my FutureProvider adapting this code from riverpod.de

test('override repositoryProvider', () async {
  final container = ProviderContainer(
    overrides: [
      // Override the behavior of repositoryProvider to return
      // FakeRepository instead of Repository.
      repositoryProvider.overrideWithProvider(Provider((ref) => FakeRepository()))
      // We do not have to override `todoListProvider`, it will automatically
      // use the overriden repositoryProvider
    ],
  );

  // The first read if the loading state
  expect(
    container.read(todoListProvider),
    const AsyncValue<List<Todo>>.loading(),
  );

  /// Wait for the request to finish
  await Future<void>.value();

  // Exposes the data fetched
  expect(container.read(todoListProvider).data.value, [
    isA<Todo>()
        .having((s) => s.id, 'id', '42')
        .having((s) => s.label, 'label', 'Hello world')
        .having((s) => s.completed, 'completed', false),
  ]);
});

In my case, it looks like this:

test('contactProvider should provide Contact for id', () async {
    final loading = container.read(contactProvider(42));
    expect(loading, const AsyncValue<Contact>.loading());
    await Future<void>.value();
    final contact = container.read(contactProvider(42)).data.value;
    expect(contact, isNotNull);
    expect(contact.id, 42);
    expect(contact.name, 'contact-name-42');
});

However, since the contact with the id ultimately comes from a db, I don't want to cache the result. When I modify my provider with autodispose, the test fails with NoSuchMethodError: The getter 'value' was called on null.

3 hours of googling and stackoverflowing and experimenting were unsuccessful.

myThorsten avatar Dec 30 '20 16:12 myThorsten

Your "await" isn't actually waiting for the future to complete. Since this provider is an autoDispose, the provider was destroyed during the "await" because there are no listeners. So the second container.read creates a new future, which wasn't awaited.

Similarly, you may want to read provider.future as it is more adapted to "awaiting" the completion

Do:

FutureProvider provider;

final container = ProviderContainer(...);

// as opposed to "read", this will keep the provider alive until the subscription is closed
final subscription = container.listen(provider.future);

await expectLater(
  subscription.read(),
  completion(
    isA<Contact>()
      .having((c) => c.id, 'id', 42)
      .having((c) => c.name, 'name', 'contact-name-42')
  ),
);

rrousselGit avatar Dec 30 '20 17:12 rrousselGit

Thank you very much for your fast reply. As far as I am concerned this answers my question perfectly. I am unsure whether I can / should close this issue or whether you want to keep it open as a reminder to perhaps enhance the documentation.

Have a good start into the New Year :)

myThorsten avatar Jan 01 '21 12:01 myThorsten

Done in 5eceb2b934d3ba6b5783df284be7213a131e2e7b

rrousselGit avatar Oct 10 '23 21:10 rrousselGit