flutter_fixtures_dio 0.3.1 copy "flutter_fixtures_dio: ^0.3.1" to clipboard
flutter_fixtures_dio: ^0.3.1 copied to clipboard

Dio implementation for Flutter Fixtures

Flutter Fixtures Dio #

pub package

Dio Fixtures Demo

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 in Action

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 #

  1. Add the package to your pubspec.yaml:
dependencies:
  flutter_fixtures_dio: ^0.3.0
  dio: ^5.4.3+1
  1. Create your fixture files in assets/fixtures/ directory

  2. Update your pubspec.yaml to include the assets:

flutter:
  assets:
    - assets/fixtures/
  1. 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
Default Selection
Automatic default selection
Pick 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 → matches GET /users
  • POST_users.json → matches POST /users
  • GET_users_123.json → matches GET /users/123
  • PUT_users_profile.json → matches PUT /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):

  1. Exact, ignoring query params: GET_search.json
  2. Values appended: GET_search_2_test.json for GET /search?q=test&page=2
  3. Literal * per value: GET_search_*_*.json
  4. {{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 collection
  • values: Array of possible responses
    • identifier: Unique identifier for this response option
    • description: Response description (first 3 characters used as HTTP status code)
    • default: Boolean indicating if this is the default response
    • data: The actual response data that will be returned
    • dataPath: (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 (or operationId) 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 inline example, the schema's example, or — when the spec carries no example at all — sample data generated from the schema ($ref, allOf, oneOf/anyOf, enum, and string formats are honoured).
  • Status ranges like 2XX map to their first code, and default maps to 500.

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 (when dataPath is 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 DioException with "No fixture found for request"
  • Empty fixture collection: Returns DioException with "No fixture options found for request"
  • No fixture selected: Returns DioException with "No fixture selected for request"
  • Processing errors: Returns DioException with 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.

UI Selector Dialog

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): Ordered HttpFixtureSource list consulted per request; the first that resolves wins and provides the response payload (default: a single HttpFileFixtureSource)
  • mockFolder (optional): Asset directory for the default file source (default: 'assets/fixtures'); ignored when sources is given
  • assetLoader (optional): Seam for reading fixture assets used by the default file source (default: root asset bundle); ignored when sources is given
  • pipeline (required): The FixturePipeline<HttpFixtureRequest> every request is served through. Its constructor is the whole configuration surface: source (an HttpFixtureSource, e.g. HttpFileFixtureSource() or HttpFixtureSources([...])), selector (DataSelectorType), view (optional DataSelectorView for user-driven selection), and delay (optional Duration, default DataSelectorDelay.instant). Build the pipeline once next to the Dio instance: remembered choices live in it. A miss rejects with a DioException whose error is the FixtureMiss (FixtureNotFound, FixtureEmpty, FixtureCancelled).

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.

1
likes
120
points
143
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Dio implementation for Flutter Fixtures

Repository (GitHub)
View/report issues
Contributing

Topics

#mock #http #testing #fixtures

License

MIT (license)

Dependencies

dio, flutter, flutter_fixtures_core

More

Packages that depend on flutter_fixtures_dio