In Riverpod 3, how do overrideWith, overrideWithValue and overrideWithBuild differ when you override a provider?
answer
- replace the implementation or the result
- full Ref access in overrideWith
- AsyncValue for Future and Stream providers
- value overrides follow scope rebuilds
- keep notifier methods, swap only build
basics
~10 soverrideWith replaces a provider's create function and keeps full Ref access; overrideWithValue replaces its result, taking an AsyncValue for Future and Stream providers; overrideWithBuild swaps only a Notifier's build while keeping its real methods.
solid answer
~40 s`overrideWith` swaps the implementation: you pass a new create function, get a full `Ref`, and Riverpod caches the result; for a `NotifierProvider` the function returns a notifier instance, typically a subclass. The source notes this override can never change after it is applied. `overrideWithValue` swaps the result: `Provider` takes a `T`, while `FutureProvider` and `StreamProvider` take an `AsyncValue<T>`, so a test can pin `AsyncLoading()`, `AsyncData(x)` or `AsyncError(e, st)`. When a rebuilt `ProviderScope` passes a new value, listeners are updated. Hand-written `NotifierProvider` has no `overrideWithValue`; generated providers do. `overrideWithBuild`, added in 3.0, replaces only a notifier's `build` — `(ref, notifier) => initial` — so the real methods still run from a chosen starting state. Both `overrideWith` and `overrideWithValue` disable auto-scoping for a provider that declared `dependencies`.
code
dart · 33 linesimport 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
class ScoreNotifier extends Notifier<int> {
@override
int build() => 0;
void correctAnswer() => state++;
}
final scoreProvider = NotifierProvider<ScoreNotifier, int>(ScoreNotifier.new);
final questionsProvider =
FutureProvider<List<String>>((ref) async => ['What is a widget?']);
void main() {
test('override kinds', () {
final container = ProviderContainer.test(
overrides: [
// Pin the async provider in an error state.
questionsProvider.overrideWithValue(
AsyncError<List<String>>(Exception('offline'), StackTrace.empty),
),
// Keep ScoreNotifier's methods, start at 9.
scoreProvider.overrideWithBuild((ref, notifier) => 9),
],
);
container.read(scoreProvider.notifier).correctAnswer();
expect(container.read(scoreProvider), 10);
expect(container.read(questionsProvider).hasError, isTrue);
});
}go deeper
Recall that overrideWithValue supplies a ready value and overrideWith supplies a new create function, both inside the overrides list.
Explain what each method replaces, why async providers take AsyncValue, how value overrides follow scope rebuilds, and what overrideWithBuild keeps.
Pick the narrowest override that keeps real logic under test, force loading and error states deterministically, and avoid changing override counts on rebuild.
Set team conventions for test seams: which providers may be overridden, when notifiers stay real, and how override choices keep tests meaningful as the codebase grows.
## Three ways to change a provider Every Riverpod override is an instruction placed in the `overrides` list of a `ProviderScope` or a `ProviderContainer`. Riverpod's documentation notes that all such methods start with `overrideWith`. Three matter in everyday tests, and they replace different things. | Method | Replaces | Signature shape | Available on | |---|---|---|---| | `overrideWith` | the whole create function or notifier | `(Ref ref) => value`, or `() => notifier` | every provider | | `overrideWithValue` | the exposed result | a value, or an `AsyncValue` for async providers | `Provider`, `FutureProvider`, `StreamProvider`, generated providers | | `overrideWithBuild` | only `Notifier.build` | `(Ref ref, notifier) => initialState` | notifier providers | ## `overrideWith`: a new implementation `overrideWith` receives a create function exactly like the provider's own. It gets a full **`Ref`**, so the fake can `ref.watch` other providers, call `ref.onDispose` or read configuration. Riverpod caches its result like any provider value. For a `NotifierProvider` the callback takes no arguments and returns a notifier — usually a subclass of the real notifier with a different `build` or stubbed methods. Two details from the source: - the override **can never change** once applied; rebuilding a `ProviderScope` with a different closure has no effect on it; - it **disables auto-scoping**, so a `dependencies` list declared on the overridden provider no longer applies, and the override itself must not specify one. ## `overrideWithValue`: a new result `overrideWithValue` skips computation entirely and exposes a value you supply. The type follows what the provider exposes: - `Provider<T>` takes a `T`; - `FutureProvider<T>` and `StreamProvider<T>` take an **`AsyncValue<T>`**, so a test can put a screen into `const AsyncLoading()`, `AsyncData(questions)` or `AsyncError(error, stackTrace)` directly, without timing a real future; - providers generated by riverpod_generator get an `overrideWithValue` too, while a hand-written `NotifierProvider` does not. Unlike `overrideWith`, a value override **tracks changes**: when a rebuilt `ProviderScope` passes a different value, the container updates it and listeners are notified. That is why the source's own example uses it to expose `Theme.of(context)` as a provider. What cannot change between rebuilds is the number of overrides — the container asserts if overrides are added or removed. ## `overrideWithBuild`: keep the methods Riverpod 3.0 added `overrideWithBuild`. It replaces only the notifier's `build`, with the signature `(ref, notifier) => initialState`. The rest of the class — `answer()`, `reset()`, validation — is the production code. That is ideal for tests that need a specific starting state, such as a quiz already at question 9 of 10, while still exercising real transitions. ## Choosing in practice 1. Replacing an I/O dependency such as a repository: `overrideWithValue(fake)` or `overrideWith((ref) => fake)`. 2. Forcing a screen into loading, data or error: `overrideWithValue` with an `AsyncValue` on the `FutureProvider`. 3. Starting a notifier mid-flow while testing its methods: `overrideWithBuild`. 4. Replacing a notifier's behaviour entirely: `overrideWith(() => FakeScoreNotifier())`, with the fake extending the real notifier. ## Same methods in widget tests and in the app Nothing about these methods is test-only. The identical `overrides` list is accepted by `ProviderScope` in `main()`, which is how apps swap implementations per environment — a mock backend for a demo build, a different base URL for staging — and by `ProviderContainer` in pure Dart code. In a widget test the list goes on the `ProviderScope` passed to `tester.pumpWidget`; in a unit test it goes on `ProviderContainer.test(overrides: [...])`. The semantics described above are the same in all three places, including the rule that a provider may appear only once per container: listing it twice triggers an `AssertionError` in debug mode. ## Mistakes that show up in review - Using `overrideWith` for a value that must follow a rebuilt scope, and wondering why the old value sticks. - Passing a bare list to a `FutureProvider`'s `overrideWithValue` instead of wrapping it in `AsyncData`. - Overriding a whole notifier when only its initial state mattered, which silently removes the logic the test was meant to cover. - Expecting a `dependencies`-based scoped provider to keep auto-scoping after it has been overridden.
- Why does overrideWithValue suit exposing a BuildContext value such as the current theme?Because a value override tracks rebuilds. A `ProviderScope` placed in `MaterialApp.builder` can pass `themeProvider.overrideWithValue(Theme.of(context))`; when the theme changes, the scope rebuilds with the new value and the container notifies listeners. An `overrideWith` closure would be applied once and never change.
- What cannot change when a ProviderScope rebuilds with a new overrides list?The set of overridden providers. The container can update existing overrides, for instance a new value in overrideWithValue, but it asserts if the rebuilt list adds or removes overrides. Conditional overrides like `if (isTest) fakeOverride` inside a scope that rebuilds are therefore a bug waiting to happen.
saying these in an interview costs you the question
- overrideWithValue on a FutureProvider takes the plain list, not an AsyncValue.
- overrideWith closures are re-applied every time the ProviderScope rebuilds.
- Overriding a whole notifier is the only way to start it from a custom state.
- Every hand-written provider, including NotifierProvider, has overrideWithValue.
- An overridden provider keeps auto-scoping through its dependencies list.