skip to content

In Riverpod 3.2 and later, why was a Notifier family's overrideWith deprecated for overrideWith2, and when do you override one member instead?

level: seniorimportance: nice to knowfreq 18%

answer

  1. family override covers every argument
  2. old callback had no argument
  3. overrideWith2 receives the argument
  4. rename planned for 4.0.0
  5. member override: provider(arg).overrideWith

basics

~20 s

overrideWith2, added in Riverpod 3.2, passes the family argument to the callback that builds a fake notifier, which the deprecated family overrideWith could not. Override one member, provider(arg).overrideWith, when only that argument should be fake.

solid answer

~40 s

Overriding a Notifier family replaces the notifier for every argument. The original family `overrideWith(NotifierT Function() create)` gave the callback no argument, so a fake could not know which member it was building — awkward now that Riverpod 3 family notifiers receive their argument in the constructor. Riverpod 3.2.0 deprecated it in favour of `overrideWith2(NotifierT Function(ArgT arg) create)`: same behaviour, but the callback gets the argument, so `quizProvider.overrideWith2(FakeQuizNotifier.new)` works; the changelog plans to rename it back to `overrideWith` in 4.0.0. Functional families were unaffected: their `overrideWith((ref, arg) => ...)` always had the argument. When only one topic should be fake, override that member — `quizProvider('history').overrideWith(() => FakeQuizNotifier('history'))` — and other arguments keep the real implementation. A family, like a provider, may be overridden only once per container.

code

dart · 45 lines
dart
import 'package:riverpod/riverpod.dart';
import 'package:test/test.dart';

class QuizNotifier extends Notifier<List<String>> {
  QuizNotifier(this.topic);
  final String topic;

  @override
  List<String> build() => const [];

  void add(String question) => state = [...state, question];
}

final quizProvider =
    NotifierProvider.family<QuizNotifier, List<String>, String>(
  QuizNotifier.new,
);

class FakeQuizNotifier extends QuizNotifier {
  FakeQuizNotifier(super.topic);

  @override
  List<String> build() => ['Fake question about $topic'];
}

void main() {
  test('every topic uses the fake', () {
    final container = ProviderContainer.test(
      overrides: [quizProvider.overrideWith2(FakeQuizNotifier.new)],
    );
    expect(
      container.read(quizProvider('history')),
      ['Fake question about history'],
    );
  });

  test('only history is faked', () {
    final container = ProviderContainer.test(
      overrides: [
        quizProvider('history').overrideWith(() => FakeQuizNotifier('history')),
      ],
    );
    expect(container.read(quizProvider('science')), isEmpty);
  });
}

go deeper

for a junior

Know that a whole family or a single member can be overridden, and that a member override only affects that argument.

for a middle

Explain why Riverpod 3 family notifiers take the argument in the constructor, and how overrideWith2 passes it to the fake while functional families already did.

for a senior

Choose family-wide versus member overrides per test, migrate deprecated calls cleanly, and avoid duplicate overrides that assert in debug mode.

for a principal

Plan for API churn across Riverpod releases: isolate override helpers in shared test utilities so the 4.0 rename is one edit instead of hundreds.

## What a family override does A **family** is a provider that keeps separate state per argument, such as one quiz per topic: `quizProvider('history')`, `quizProvider('science')`. Tests can override it at two granularities: - **the whole family**, which replaces the notifier or create function for every argument; - **one member**, `quizProvider('history')`, which replaces only that argument's provider. ## Why `overrideWith` was deprecated for Notifier families Riverpod 3.0 merged `FamilyNotifier` into `Notifier`: a family notifier now receives its argument through its **constructor**, as in `NotifierProvider.family<QuizNotifier, List<String>, String>(QuizNotifier.new)` with `QuizNotifier(this.topic)`. The family-level override, however, kept the signature `overrideWith(NotifierT Function() create)` — a callback with **no argument**. A fake built from it could not know which topic it was standing in for, so tests resorted to one override per member or to fakes that ignored the argument. Riverpod **3.2.0** fixed that: | API | Callback | Status in 3.4.3 | |---|---|---| | family `overrideWith` | `NotifierT Function()` | `@Deprecated('Use overrideWith2 instead')` | | family `overrideWith2` | `NotifierT Function(ArgT arg)` | current | | family `overrideWithBuild` | `(ref, notifier) => state` | current | | functional family `overrideWith` | `(Ref ref, ArgT arg) => value` | current, never deprecated | The changelog says the behaviour is the same and that in 4.0.0 `overrideWith2` will be renamed to `overrideWith`. With a constructor-taking fake, the override becomes a tear-off: `quizProvider.overrideWith2(FakeQuizNotifier.new)`. For a family generated by riverpod_generator, the argument bundles the `build` parameters — a record such as `(String, {int limit})` when there are several — and the same override methods are available on non-generic generated families. ## Overriding one member A family member is itself a provider, so it has the provider-level override methods. `quizProvider('history').overrideWith(() => FakeQuizNotifier('history'))` replaces only the history quiz; `quizProvider('science')` still runs the real notifier. This is the right choice when: 1. the test is about one screen showing one argument and the rest should behave normally; 2. different arguments need different fakes, for instance one topic that fails to load and one that loads; 3. the real implementation is cheap and safe for the other arguments. The family-wide override is better when every argument would otherwise hit real I/O, which is the common case in widget tests of a list of topics. ## Rules the container enforces - A container may override a given **family only once**; a second family override triggers an `AssertionError` in debug mode ("Tried to override a family twice within the same container"). - The same applies to a single provider, including a family member, overridden twice. - When a `ProviderScope` rebuilds, overrides can be updated but not added or removed. ## Designing fakes for families Because the fake now receives the argument, it can vary its behaviour per member without separate overrides: - return canned questions for known topics and an empty list for the rest; - throw for one designated topic to exercise the error path next to working ones; - record which arguments were requested, so a test can assert that opening the history screen only loaded `history`. Keep the fake a subclass of the real notifier and override only what the test needs — usually `build` — so methods such as `add` still run the production logic. When only the starting state matters, the family's `overrideWithBuild((ref, notifier) => ...)` is lighter still: the callback can read `notifier.topic` and return a state for it, and no fake class is needed at all. ## Migrating existing tests 1. Search the test suite for family-level `overrideWith(` on Notifier families; the analyzer's deprecation warnings list them. 2. Replace each with `overrideWith2`, using the argument instead of a hard-coded one. 3. Leave functional families and single-member overrides alone; neither is deprecated. 4. Expect a mechanical rename back to `overrideWith` when Riverpod 4 ships, as the changelog announces.

  • Why must FakeQuizNotifier extend QuizNotifier rather than implement it?
    Riverpod's testing guide says a Notifier mock must subclass the notifier's base class, because implementing the Notifier interface breaks what the provider expects from it. Extending the real notifier keeps that machinery and lets the fake override only `build` or specific methods.
  • How would you fake a functional family such as FutureProvider.family?
    Use the family's `overrideWith((ref, arg) => ...)`, which has always received the argument and is not deprecated. For example `questionsForTopicProvider.overrideWith((ref, topic) async => ['Fake $topic question'])`. A single member can instead take `overrideWithValue(AsyncData(...))`.

saying these in an interview costs you the question

  • overrideWith2 behaves differently from overrideWith beyond passing the argument.
  • Overriding quizProvider('history') also replaces every other topic.
  • Functional families' overrideWith was deprecated along with Notifier families'.
  • Overriding the same family twice in one container lets the last override win.
  • Family notifiers in Riverpod 3 still receive the argument through build(arg).