Flutter Fixtures Dio
Seamless Dio request interception with fixture files
Dio HTTP client implementation for the Flutter Fixtures library. This package provides a seamless way to intercept Dio requests and return mock responses from fixture files.
Quick Start
Add the dependency to your pubspec.yaml:
dependencies:
flutter_fixtures_dio: ^0.3.0
dio: ^5.4.3+1
Set up the interceptor:
import 'package:dio/dio.dart';
import 'package:flutter_fixtures_dio/flutter_fixtures_dio.dart';
final dio = Dio();
dio.interceptors.add(
FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFileFixtureSource(),
selector: DataSelectorType.random,
),
),
);
That's it! Your Dio requests will now return mock responses from fixture files.
Dio interceptor returning mock responses from fixture files
What's Included
This package provides one main component:
FixturesInterceptor
A Dio interceptor that automatically intercepts HTTP requests and returns mock responses. It consults an ordered list of HttpFixtureSources — fixture files by default, an OpenAPI spec or your own source if you add one.
Installation
- Add the package to your
pubspec.yaml:
dependencies:
flutter_fixtures_dio: ^0.3.0
dio: ^5.4.3+1
-
Create your fixture files in
assets/fixtures/directory -
Update your
pubspec.yamlto include the assets:
flutter:
assets:
- assets/fixtures/
- Run
flutter pub get
Basic Usage
Simple Setup
The most basic setup requires just a few lines of code:
import 'package:dio/dio.dart';
import 'package:flutter_fixtures_dio/flutter_fixtures_dio.dart';
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
// Add the fixtures interceptor
dio.interceptors.add(
FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFileFixtureSource(),
selector: DataSelectorType.random,
delay: DataSelectorDelay.instant,
),
),
);
// Use Dio as normal - requests will return mock data
final response = await dio.get('/users');
print(response.data); // Mock data from fixture file
Fixture Selection Strategies
Choose how fixtures are selected:
// Always use the default fixture (marked with "default": true)
selector: DataSelectorType.defaultValue
// Randomly select from available fixtures
selector: DataSelectorType.random
// Let user pick through UI (requires flutter_fixtures_ui package)
selector: DataSelectorType.pick
Automatic default selection |
User-driven selection |
Custom Asset Directory
By default, fixtures are loaded from assets/fixtures/. You can customize this:
dio.interceptors.add(
FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFileFixtureSource(mockFolder: 'assets/my_mocks'),
selector: DataSelectorType.random,
),
),
);
Fixture Files
File Naming Convention
Fixture files should be named using the pattern: {HTTP_METHOD}_{PATH}.json
Examples:
GET_users.json→ matchesGET /usersPOST_users.json→ matchesPOST /usersGET_users_123.json→ matchesGET /users/123PUT_users_profile.json→ matchesPUT /users/profile
Note: Forward slashes (/) in paths are replaced with underscores (_) in filenames.
Query Parameter Matching
For requests with query parameters, candidates are tried in this priority order (query values are ordered by sorted key name, not URL order):
- Exact, ignoring query params:
GET_search.json - Values appended:
GET_search_2_test.jsonforGET /search?q=test&page=2 - Literal
*per value:GET_search_*_*.json {{key}}per sorted key:GET_search_{{page}}_{{q}}.json
The * and {{key}} forms are literal file names, not globs — they match
any request with the same number of non-empty query values. The first
candidate that exists wins.
Fixture File Structure
Each fixture file contains multiple response options:
{
"description": "User API responses",
"values": [
{
"identifier": "success",
"description": "200 Success",
"default": true,
"data": {
"users": [
{"id": 1, "name": "Alice Johnson", "email": "alice@example.com"},
{"id": 2, "name": "Bob Smith", "email": "bob@example.com"}
]
}
},
{
"identifier": "empty",
"description": "200 Empty List",
"data": {
"users": []
}
},
{
"identifier": "server_error",
"description": "500 Server Error",
"data": {
"error": "Internal server error",
"message": "Something went wrong"
}
}
]
}
Field Descriptions
description: Human-readable description of the fixture collectionvalues: Array of possible responsesidentifier: Unique identifier for this response optiondescription: Response description (first 3 characters used as HTTP status code)default: Boolean indicating if this is the default responsedata: The actual response data that will be returneddataPath: (Optional) Path to external JSON file containing response data
External Data Files
For large responses, you can store data in separate files:
{
"description": "Large user dataset",
"values": [
{
"identifier": "large_dataset",
"description": "200 Success",
"default": true,
"dataPath": "data/users_large.json"
}
]
}
The dataPath is relative to your fixture folder (e.g., assets/fixtures/data/users_large.json).
OpenAPI Fixtures
If your API has an OpenAPI 3.x spec, you don't have to hand-write a fixture
file per endpoint. Drop the spec's JSON in your assets and add an
OpenApiFixtureSource to the interceptor's sources:
dio.interceptors.add(
FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFixtureSources([
HttpFileFixtureSource(),
OpenApiFixtureSource(specPath: 'assets/fixtures/openapi.json'),
]),
selector: DataSelectorType.pick,
),
),
);
Remember to include the file in your pubspec.yaml assets (the default
assets/fixtures/ entry already covers the path above).
For any request with no matching fixture file, the operation is looked up in
the spec (path templates like /users/{id} and servers base paths are
handled) and its documentation becomes the collection:
- The operation's
summary(oroperationId) names the collection. - Each response becomes a selectable document, described as
"<status> <response description>"— e.g.404 Product not found. - Payloads come from the response's named
examples(one document each), its inlineexample, the schema'sexample, or — when the spec carries no example at all — sample data generated from the schema ($ref,allOf,oneOf/anyOf,enum, and stringformats are honoured). - Status ranges like
2XXmap to their first code, anddefaultmaps to500.
Sources are consulted in list order and the first one that resolves wins:
with the list above, hand-written fixture files beat spec-derived ones, so
you can start from the spec and override individual endpoints with richer
fixtures as you need them. Any HttpFixtureSource implementation can join
the list — files and OpenAPI are the built-in ones, and you can plug in
your own for other API description formats.
Advanced Usage
Response Headers
The interceptor automatically adds helpful headers to responses:
x-fixture-file-path: Path to the fixture file used (whendataPathis specified)
final response = await dio.get('/users');
final fixturePath = response.headers.value('x-fixture-file-path');
print('Response from: $fixturePath');
Error Handling
The interceptor handles various error scenarios:
- No fixture found: Returns
DioExceptionwith "No fixture found for request" - Empty fixture collection: Returns
DioExceptionwith "No fixture options found for request" - No fixture selected: Returns
DioExceptionwith "No fixture selected for request" - Processing errors: Returns
DioExceptionwith detailed error information
Integration with UI Components
For interactive fixture selection, combine with the flutter_fixtures_ui package:
import 'package:flutter_fixtures_ui/flutter_fixtures_ui.dart';
dio.interceptors.add(
FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFileFixtureSource(),
selector: DataSelectorType.pick,
view: FixturesDialogView.of(context),
),
),
);
This will show a dialog allowing users to choose which fixture response to return.
Interactive fixture selection with UI dialog
Examples
Complete Example
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:flutter_fixtures_dio/flutter_fixtures_dio.dart';
class ApiService {
late final Dio _dio;
ApiService() {
_dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
// Add fixtures interceptor for development/testing
_dio.interceptors.add(
FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFileFixtureSource(),
selector: DataSelectorType.defaultValue,
),
),
);
}
Future<List<User>> getUsers() async {
final response = await _dio.get('/users');
return (response.data['users'] as List)
.map((json) => User.fromJson(json))
.toList();
}
Future<User> createUser(User user) async {
final response = await _dio.post('/users', data: user.toJson());
return User.fromJson(response.data);
}
}
API Reference
FixturesInterceptor
The main interceptor class that handles request interception.
Constructor Parameters:
sources(optional): OrderedHttpFixtureSourcelist consulted per request; the first that resolves wins and provides the response payload (default: a singleHttpFileFixtureSource)mockFolder(optional): Asset directory for the default file source (default:'assets/fixtures'); ignored whensourcesis givenassetLoader(optional): Seam for reading fixture assets used by the default file source (default: root asset bundle); ignored whensourcesis givenpipeline(required): TheFixturePipeline<HttpFixtureRequest>every request is served through. Its constructor is the whole configuration surface:source(anHttpFixtureSource, e.g.HttpFileFixtureSource()orHttpFixtureSources([...])),selector(DataSelectorType),view(optionalDataSelectorViewfor user-driven selection), anddelay(optionalDuration, defaultDataSelectorDelay.instant). Build the pipeline once next to the Dio instance: remembered choices live in it. A miss rejects with aDioExceptionwhoseerroris theFixtureMiss(FixtureNotFound,FixtureEmpty,FixtureCancelled).
Related Packages
- flutter_fixtures: Complete Flutter Fixtures library with all components
- flutter_fixtures_core: Core interfaces and models
- flutter_fixtures_ui: UI components for fixture selection
Contributing
Contributions are welcome! Please read our contributing guide and submit pull requests to our GitHub repository.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Where did this response come from?
Both interceptors stamp served responses, and ResponseOrigin.of reads the
stamp once so apps and logging never parse headers themselves:
switch (ResponseOrigin.of(response)) {
case FixtureOrigin(:final document, :final filePath): // served fixture
case ReplayOrigin(:final recordedAt): // replayed recording
case LiveOrigin(): // network or elsewhere
}
A request that produced no response carries its case in
DioException.error: a FixtureMiss (FixtureNotFound, FixtureEmpty,
FixtureCancelled) or a replay rejection.
Record & replay
This package also ships RecorderInterceptor, the Dio adapter for the
Flutter Fixtures record & replay module: capture real HTTP traffic while
exercising the app, then replay it later in recorded order — without
touching the network. The engine and UI tools live in
flutter_fixtures_recorder; this
interceptor only talks to the thin TrafficRecorder seam in core.
final recorder = FixtureRecorder(store: MemoryRecordingSessionStore());
dio.interceptors.add(RecorderInterceptor(recorder: recorder));
Composed with FixturesInterceptor (recorder first), the two features
chain: fixture responses — including ones picked by hand through the
dialog — are recorded into the session, and replaying serves the same
choices back in order with no dialogs and no fixture pipeline involved:
dio.interceptors
..add(RecorderInterceptor(recorder: recorder))
..add(FixturesInterceptor(
pipeline: FixturePipeline(
source: HttpFileFixtureSource(),
selector: DataSelectorType.pick,
view: FixturesDialogView(contextProvider: () => context),
),
));
Replayed responses behave like the live ones did: they flow through the
response-interceptor chain, an error status raises DioException.badResponse
as the original did, and each is stamped so ResponseOrigin.of reports a
ReplayOrigin.
See the recorder package README for sessions, storage, ordering semantics, and the built-in UI tools.