Why does a mocktail stub using any() on a custom parameter type throw a StateError, and how does registerFallbackValue fix it?
answer
- any() must return a real T
- null is illegal for non-nullable
- built-in fallbacks for primitives only
- once per type in setUpAll
- a Fake makes a cheap dummy
basics
~20 smocktail's any() must hand back a real value of the parameter's type while it captures the call, and it only knows fallbacks for primitives and core collections; registerFallbackValue(FakeForecastRequest()) in setUpAll supplies one for your type.
solid answer
~30 sWhen you write `when(() => api.fetch(any()))`, the closure actually calls `fetch`, so `any()` has to return something of the parameter's type — `null` would be a `TypeError` for a non-nullable `ForecastRequest`. mocktail ships fallback values for `bool`, `int`, `double`, `String`, `List`, `Map`, `Set`, `DateTime` and function types; for anything else it throws a `StateError` that tells you to call `registerFallbackValue`. You call `registerFallbackValue(FakeForecastRequest())` once per type, normally in `setUpAll`, where `class FakeForecastRequest extends Fake implements ForecastRequest {}` is enough because the value is only passed around, never used. It does not change what the stub matches.
code
dart · 22 linesimport 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockWeatherApi extends Mock implements WeatherApi {}
class FakeForecastRequest extends Fake implements ForecastRequest {}
void main() {
setUpAll(() {
registerFallbackValue(FakeForecastRequest());
});
test('any() works for a custom parameter type', () async {
final api = MockWeatherApi();
when(() => api.fetch(any()))
.thenAnswer((_) async => const Forecast(highC: 14, lowC: 6));
final forecast = await api.fetch(const ForecastRequest(city: 'Oslo'));
expect(forecast.highC, 14);
});
}go deeper
Remember the rule: a custom class used with any() needs registerFallbackValue once, usually with a Fake subclass, in setUpAll.
Explain the mechanism: when runs the closure, sound null safety forbids null for the parameter, so any() must return a real instance of the type.
Diagnose quickly from the message: fallback StateError, Null TypeError from a missing or mis-typed stub, or an extension method that mocktail cannot intercept.
Decide on shared test scaffolding, such as one setUpAll helper registering fallbacks for the domain types every suite uses.
## The error A common first surprise with **mocktail** (a Dart mocking library that works without code generation) is a failure like this while *setting up* a stub: ```text Bad state: A test tried to use `any` or `captureAny` on a parameter of type `ForecastRequest`, but registerFallbackValue was not previously called to register a fallback value for `ForecastRequest`. ``` `Bad state:` is how Dart prints a `StateError`. The test fails before the code under test even runs. ## Why `any()` needs a value mocktail's `when` takes a closure — `when(() => api.fetch(any()))` — and **executes** it while the mock is in capture mode. That means the real Dart call `api.fetch(...)` happens, with whatever `any()` returned as the argument. Under **sound null safety**, a parameter declared `ForecastRequest` (non-nullable) cannot receive `null`. So `any<ForecastRequest>()` must return an actual `ForecastRequest` instance, purely to keep the type system happy; the matcher it registers is what the stub really uses. mocktail keeps a list of **fallback values**. Out of the box it holds: - `false`, `42`, `42.0` and `'42'` for the primitives, - empty `List`, `Map` and `Set` constants, - a `DateTime`, - a dummy callback for function-typed parameters. For any other type the lookup fails and mocktail throws the `StateError` above. ## The fix Register one fallback per custom type, once, before the stubs run: ```dart class FakeForecastRequest extends Fake implements ForecastRequest {} void main() { setUpAll(() { registerFallbackValue(FakeForecastRequest()); }); } ``` Points worth saying in an interview: 1. **Once per type** is enough; the registry is global to the test isolate, so `setUpAll` is the conventional place. 2. The fallback is **never interacted with** — mocktail only passes it into the capturing call. That is why a `Fake` works: `Fake` (from `package:test_api`, re-exported by mocktail) throws `UnimplementedError` from any member you did not override, and nothing calls those members. 3. A real instance works too (`registerFallbackValue(const ForecastRequest(city: 'x'))`) when the class is cheap to construct. 4. The fallback does **not** influence matching. `any()` still matches every argument; the fallback is invisible to the assertion. 5. The same rule applies to `captureAny()` and to `any(named: 'request')`. ## A related trap: generics A method like `T read<T>(String key)` can defeat inference. If `any()` cannot infer `T`, the type argument becomes `dynamic`, the stub is registered for `read<dynamic>`, and the real call `read<Forecast>` finds no stub — so the mock returns `null`. Pass the type explicitly: `when(() => cache.read<Forecast>(any()))`. ## How this compares with mockito | | mocktail | mockito (with code generation) | |---|---|---| | Matcher syntax | `any()` inside a closure | `any` passed directly | | Why `null` is not a problem | fallback values | generated overrides widen parameters to nullable | | Custom types | `registerFallbackValue` | handled by the generated code | | Unknown return types | the stub closure catches the `TypeError` | `provideDummy` / `fallbackGenerators` | mockito's generated mock classes override each method with nullable parameter types, so its `any` can simply be `null`. mocktail has no generated code, so it needs a real instance instead. ## Reading the symptoms - **StateError mentioning `registerFallbackValue`** — a custom type used with `any()` or `captureAny()` and no fallback registered. - **`type 'Null' is not a subtype of type 'Future<Forecast>'`** — a different problem: the call reached the mock without a matching stub, often because a generic stub was registered for `dynamic` or the literal arguments did not match. - **`No method stub was called from within when()`** — the closure called something that is not a mock member, such as an extension method, which mocktail cannot intercept. Getting these three apart quickly is what separates someone who has used mocktail from someone who has read about it. ## Organising fallbacks in a larger suite In a project with dozens of test files, the same domain types — requests, entities, value objects — keep appearing as parameters of mocked repositories. Two habits keep the fallback noise down: - keep the `Fake` classes (`FakeForecastRequest`, `FakeLocation`) in one shared test helper file rather than redeclaring them per test; - expose a single `registerDomainFallbacks()` function from that file and call it from each suite's `setUpAll`. Registering a type twice is harmless, so a shared helper can register generously. What it cannot do is remove the need: every custom type passed to `any()` or `captureAny()` somewhere must be covered before that stub runs.
- Why does mocktail recommend putting registerFallbackValue in setUpAll rather than setUp?The fallback registry is global and is only needed once per type, so registering it once for the file is enough. Calling it in `setUp` works but repeats the registration before every test for no benefit.
- Can the fallback value change which calls the stub matches?No. `any()` registers an `anything` matcher; the fallback only exists so the capturing call type-checks. To restrict matching you use a literal argument or `any(that: matcher)`, never a particular fallback instance.
saying these in an interview costs you the question
- Thinks the fallback value is what the stub matches against
- Makes the production parameter nullable to silence the error
- Registers the fallback inside every individual test body
- Confuses the StateError with the Null-is-not-a-subtype TypeError
- Believes mocktail needs a fallback for int and String parameters too