skip to content

Mocks & Fakes

mocktail stubs with when and thenAnswer and needs no codegen, while mockito generates mocks with build_runner. Interviewers ask when a hand-written fake beats a mock, and how to stub a channel.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

6

With mocktail in a Flutter test, how do you stub a dependency's async method and then verify the code under test called it?

level: middleimportance: must knowfreq 58%

answer

  1. extends Mock implements the interface
  2. stub inside a closure
  3. futures go through thenAnswer
  4. verify(...).called(n)
  5. verifyNever, not called(0)

basics

~10 s

Declare class MockWeatherApi extends Mock implements WeatherApi, stub with when(() => api.fetchForecast('Oslo')).thenAnswer((_) async => forecast), run the code, then assert with verify(() => api.fetchForecast('Oslo')).called(1).

solid answer

~40 s

mocktail needs no code generation: `class MockWeatherApi extends Mock implements WeatherApi {}` gives a mock whose every member routes through `noSuchMethod`. You stub with `when(() => api.fetchForecast('Oslo'))` — the call sits inside a closure — and finish with `thenReturn` for plain values, `thenAnswer((_) async => forecast)` for a `Future` or `Stream`, or `thenThrow` for the error path; `thenReturn` given a Future throws an `ArgumentError`. After exercising the view model you assert with `verify(() => api.fetchForecast('Oslo')).called(1)`, or `verifyNever(...)` for zero calls, because `called(0)` does not work. Argument matchers are `any()` and `any(named: 'units')`. An unstubbed member returns `null`, so a missing stub on a `Future`-returning method fails with a `TypeError`.

code

dart · 32 lines
dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockWeatherApi extends Mock implements WeatherApi {}

void main() {
  late MockWeatherApi api;
  late ForecastViewModel viewModel;

  setUp(() {
    api = MockWeatherApi();
    viewModel = ForecastViewModel(api);
  });

  test('load exposes the fetched forecast', () async {
    when(() => api.fetchForecast('Oslo'))
        .thenAnswer((_) async => const Forecast(highC: 14, lowC: 6));

    await viewModel.load('Oslo');

    expect(viewModel.forecast?.highC, 14);
    verify(() => api.fetchForecast('Oslo')).called(1);
  });

  test('load surfaces an error when the API throws', () async {
    when(() => api.fetchForecast(any())).thenThrow(Exception('offline'));

    await viewModel.load('Oslo');

    expect(viewModel.error, isNotNull);
  });
}

go deeper

for a junior

Recall the four moves: extend Mock and implement the interface, stub with when and a closure, run the code, then verify with called(n).

for a middle

Explain why futures need thenAnswer, how any() and any(named:) match, why called(0) is replaced by verifyNever, and why an unstubbed call returns null.

for a senior

Show judgment about what to verify: interactions that are the behaviour, not incidental calls, and how over-verification makes a view-model test brittle under refactoring.

for a principal

Weigh a shared convention: mocks per test versus shared fakes, and a rule for when interaction verification is allowed in a codebase's test suite.

## What mocktail is **mocktail** (version 1.x) is a mocking library for Dart and Flutter tests. It creates a **mock**: an object that implements your dependency's interface, records every call made on it, and returns whatever you configured. Its selling point is that it needs **no code generation** — you write one line per mock class and never run a build step. ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; class MockWeatherApi extends Mock implements WeatherApi {} ``` `Mock` overrides `noSuchMethod`. Because `MockWeatherApi` declares no bodies of its own, every call to a `WeatherApi` member lands in `noSuchMethod`, where mocktail either records the call or looks up a stub. ## Stubbing: `when` plus a response A **stub** tells the mock what to return for a matching call. mocktail's `when` takes a **closure**, not the call's result: ```dart final api = MockWeatherApi(); when(() => api.fetchForecast('Oslo')) .thenAnswer((_) async => const Forecast(highC: 14, lowC: 6)); ``` The three responses: - `thenReturn(value)` — returns a fixed, synchronous value. Passing a `Future` or `Stream` throws an `ArgumentError` telling you to use `thenAnswer` instead. - `thenAnswer((invocation) => ...)` — runs a function on every call, so it is the right tool for `Future`s, `Stream`s and answers computed from the arguments. - `thenThrow(error)` — throws, which is how you drive the view model's error branch. When several stubs match the same call, the **most recently registered** one wins, so a test can override a stub set in `setUp`. ## Argument matchers Literal arguments must be equal to match. For looser matching: - `any()` for a positional argument and `any(named: 'units')` for a named one. - `any(that: startsWith('O'))` to apply a `package:matcher` matcher. - `captureAny()` inside `verify` to read back the arguments that were passed. Using `any()` on a parameter of a custom class needs `registerFallbackValue` for that type first; primitives, `List`, `Map`, `Set` and `DateTime` are covered out of the box. ## Verifying interactions After running the code under test, `verify` checks what happened: ```dart await viewModel.load('Oslo'); verify(() => api.fetchForecast('Oslo')).called(1); verifyNever(() => api.fetchForecast('Bergen')); verifyNoMoreInteractions(api); ``` Details interviewers probe: 1. `called` accepts an exact count or a matcher such as `greaterThan(1)`. 2. `called(0)` does not work as expected; mocktail's own docs say to use `verifyNever`. 3. A verified invocation is **excluded from later verifications**, so a second `verify(...).called(1)` only counts calls made since the first. 4. `verifyInOrder([...])` checks order; `untilCalled(() => ...)` returns a `Future` that completes when the call happens. 5. `reset(api)` clears both stubs and recorded calls. ## The failure you meet first An unstubbed member returns `null` by default. For a method whose return type is non-nullable — `Future<Forecast>` — that surfaces as a `TypeError` such as `type 'Null' is not a subtype of type 'Future<Forecast>'`, thrown where the view model calls the mock. The fix is a stub, not a nullable signature. `throwOnMissingStub(mock)` makes unstubbed calls throw a clearer error instead. | Goal | mocktail call | |---|---| | Return a plain value | `thenReturn(value)` | | Return a `Future` or `Stream` | `thenAnswer((_) async => value)` | | Drive the error path | `thenThrow(Exception('offline'))` | | Assert exactly n calls | `verify(() => ...).called(n)` | | Assert no call | `verifyNever(() => ...)` | ## Common mistakes in review - **Stubbing after the call.** `when` must run before the code under test calls the mock; a stub registered afterwards affects only later calls. - **Literal arguments that never match.** `when(() => api.fetchForecast('oslo'))` does not match a call with `'Oslo'`; the mock then returns `null` and the failure appears far from the typo. Use `any()` when the argument is not what the test is about. - **Stubbing an extension method.** Extension methods are resolved statically and never reach `noSuchMethod`, so mocktail cannot intercept them: the real extension runs, and `when` typically reports that no method stub was called. Stub the instance member the extension uses instead. - **One mock shared across tests without a fresh instance.** Create the mock in `setUp`, or call `reset` between tests, so stubs and recorded calls do not leak. ## Where it fits In a Flutter widget test the mock is handed to the view model — directly, or through whatever dependency injection the app uses — and the widget is pumped with `testWidgets`. The mock only replaces the collaborator; `flutter_test` still drives the widget tree. Keep verification for interactions that *are* the behaviour (the view model fetched once, it did not retry); assert on visible state for everything else, or the test breaks whenever the implementation is refactored.

  • Why does mocktail take a closure in when() and verify() where mockito takes the call itself?
    The closure lets mocktail run the call while it is in "capture" mode and catch the `TypeError` that a `null` return would cause for a non-nullable return type. Without code generation there is no generated override to widen types, so deferring the call inside a closure is how mocktail stays null-safe.
  • A test verifies called(1) twice in a row after two calls to fetchForecast. Why might the second verify fail?
    mocktail excludes invocations from later verifications once they have been verified. If the first `verify` was `called(2)`, both calls are consumed and a second `verify` finds none. If it was `called(1)`, it fails outright because two calls matched. Verify each interaction once, with the count you actually expect.
  • How do you stub a method that returns a Stream with mocktail?
    Use `thenAnswer((_) => Stream.value(forecast))` or return a `StreamController`'s stream from the answer. `thenReturn` rejects a `Stream` with an `ArgumentError` — mocktail's source cites Zone considerations — and `thenAnswer` also builds a fresh stream per call, which matters for single-subscription streams.

saying these in an interview costs you the question

  • Uses thenReturn(Future.value(x)) for an async method
  • Writes when(api.fetchForecast('Oslo')) without a closure in mocktail
  • Asserts zero calls with verify(...).called(0)
  • Believes mocktail needs build_runner to generate mock classes
  • Thinks an unstubbed mock method throws a descriptive missing-stub error by default
  • Verifies every call, so any refactor of the view model breaks the test
open as a page

With Dart's package:http, how do you test Flutter code that makes HTTP requests without sending anything over the network?

level: juniorimportance: should knowfreq 38%

basics

~10 s

Inject an http.Client into the class that makes requests, and in tests pass MockClient from package:http/testing.dart, whose handler receives each Request and returns an http.Response, such as Response(body, 200).

open as a page

How does mockito's @GenerateNiceMocks or @GenerateMocks code generation differ from mocktail, and what decides which one a Flutter team uses?

level: middleimportance: should knowfreq 45%

basics

~10 s

mockito generates mock classes into a .mocks.dart file from @GenerateNiceMocks or @GenerateMocks when build_runner runs; mocktail needs no generation and uses closures plus registerFallbackValue. The choice is mostly build step versus runtime setup.

open as a page

Why does a mocktail stub using any() on a custom parameter type throw a StateError, and how does registerFallbackValue fix it?

level: middleimportance: should knowfreq 42%

basics

~20 s

mocktail'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.

open as a page

In a Flutter test, how do you stub a MethodChannel so code that calls a plugin runs without the native side?

level: middleimportance: should knowfreq 36%

basics

~10 s

Register a handler on the test messenger: TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) async => ...), return a value per call.method, throw PlatformException for errors, and pass null to remove it.

open as a page

In Flutter tests of a forecast view model, when does a hand-written fake WeatherApi serve better than a mocktail mock, and how do you build it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Prefer a hand-written class that implements WeatherApi with in-memory data when many tests share the dependency or assert on resulting state; keep mocktail mocks for one-off stubs and for interactions that are themselves the behaviour.

open as a page