flutter_fixtures_dio 0.3.1
flutter_fixtures_dio: ^0.3.1 copied to clipboard
Dio implementation for Flutter Fixtures
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.