joker 0.1.0-dev.1
joker: ^0.1.0-dev.1 copied to clipboard
HTTP request stubbing and mocking library for Dart. Intercept HTTP requests from any library using HttpOverrides.
Joker ๐ #

A powerful HTTP request stubbing and mocking library for Dart. Joker allows you to intercept HTTP requests made by any Dart library that uses the standard HttpClient and HttpOverrides classes, including popular packages like dio and http.
Features #
- ๐ฏ Universal HTTP Interception: Works with any Dart HTTP library that uses
HttpClient - ๐ง Flexible URL Matching: Match requests by host, path, and HTTP method
- ๐ JSON Response Stubbing: Built-in support for JSON responses
- โก Custom Responses: Full control over status codes, headers, and response bodies
- ๐ One-time Stubs: Automatically remove stubs after use
- ๐ Named Stubs: Organize and manage stubs with custom names
- โฑ๏ธ Response Delays: Simulate network latency with custom delays
- ๐งช Test-Friendly: Perfect for unit and integration tests
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
joker: ^0.1.0-dev.1
For testing only:
dev_dependencies:
joker: ^0.1.0-dev.1
Then run:
dart pub get
Quick Start #
import 'package:joker/joker.dart';
import 'dart:io';
Future<void> main() async {
// Start intercepting HTTP requests
Joker.start();
// Stub a JSON response
Joker.stubJson(
host: 'api.example.com',
path: '/users',
data: {'users': [{'id': 1, 'name': 'John Doe'}]},
);
// Make an HTTP request - it will be intercepted
final client = HttpClient();
final request = await client.getUrl(Uri.parse('https://api.example.com/users'));
final response = await request.close();
// The response will contain the stubbed data
print('Status: ${response.statusCode}'); // 200
// Always stop intercepting when done
Joker.stop();
}
Usage Examples #
Basic JSON Stubbing #
// Start Joker
Joker.start();
// Stub a simple GET request
Joker.stubJson(
host: 'api.example.com',
path: '/user',
data: {'id': 123, 'name': 'John Doe'},
);
// The request will return the stubbed data
final response = await http.get(Uri.parse('https://api.example.com/user'));
Multiple Endpoints #
Joker.start();
// Stub multiple endpoints
Joker.stubJson(
host: 'api.example.com',
path: '/users',
data: {'users': []},
);
Joker.stubJson(
host: 'api.example.com',
path: '/posts',
data: {'posts': []},
);
Custom Response Configuration #
Joker.stubJson(
host: 'api.example.com',
path: '/users',
method: 'POST',
data: {'id': 1, 'created': true},
statusCode: 201,
headers: {'X-Custom-Header': 'value'},
delay: Duration(milliseconds: 500), // Simulate network delay
);
Named Stubs for Organization #
// Create named stubs for better management
Joker.stubJson(
host: 'api.example.com',
path: '/users',
data: {'users': []},
name: 'empty-users',
);
// Later, remove stubs by name
Joker.removeStubsByName('empty-users');
One-time Stubs #
// Stub that automatically removes itself after first use
Joker.stubJson(
host: 'api.example.com',
path: '/auth',
data: {'token': 'abc123'},
removeAfterUse: true,
);
// First request gets the stubbed response
await http.get(Uri.parse('https://api.example.com/auth'));
// Second request will not be intercepted (stub was removed)
await http.get(Uri.parse('https://api.example.com/auth'));
Advanced Custom Responses #
import 'package:joker/joker.dart';
// Create a custom response with full control
final customResponse = JokerResponse(
statusCode: 404,
headers: {'Content-Type': 'application/json'},
body: '{"error": "Not found"}',
delay: Duration(seconds: 1),
);
Joker.stubUrl(
host: 'api.example.com',
path: '/missing',
response: customResponse,
);
API Reference #
Core Methods #
Joker.start()
Starts intercepting HTTP requests. Must be called before any stubbing takes effect.
Joker.stop()
Stops intercepting HTTP requests and clears all stubs. Restores normal HTTP behavior.
Joker.stubJson({...})
Creates and registers a stub for JSON responses.
Parameters:
host(String?): The host to match (e.g., 'api.example.com')path(String?): The path to match (e.g., '/users')method(String?): HTTP method to match (e.g., 'GET', 'POST')data(Map<String, dynamic>): The JSON data to returnstatusCode(int): HTTP status code (default: 200)headers(Map<String, String>): Additional headersdelay(Duration?): Artificial delay before respondingname(String?): Optional name for the stubremoveAfterUse(bool): Remove stub after first match (default: false)
Joker.stubUrl({...})
Creates and registers a stub with a custom response.
Parameters:
host,path,method: URL matching criteriaresponse(JokerResponse): Custom response objectname(String?): Optional name for the stubremoveAfterUse(bool): Remove stub after first match
Stub Management #
Joker.clearStubs()
Removes all registered stubs.
Joker.removeStub(JokerStub stub)
Removes a specific stub. Returns true if removed.
Joker.removeStubsByName(String name)
Removes all stubs with the given name. Returns the number of removed stubs.
Response Types #
JokerResponse
Represents a response that will be returned for matched requests.
// Basic response
JokerResponse(
statusCode: 200,
headers: {'Content-Type': 'text/plain'},
body: 'Hello World',
delay: Duration(milliseconds: 100),
)
// JSON response (convenience factory)
JokerResponse.json(
{'message': 'Hello'},
statusCode: 200,
headers: {'X-Custom': 'value'},
delay: Duration(milliseconds: 100),
)
Testing Integration #
Joker is perfect for testing scenarios where you need to mock HTTP responses:
import 'package:test/test.dart';
import 'package:joker/joker.dart';
void main() {
group('API Tests', () {
setUp(() {
Joker.start();
});
tearDown(() {
Joker.stop();
Joker.clearStubs();
});
test('should handle successful user fetch', () async {
// Arrange
Joker.stubJson(
host: 'api.example.com',
path: '/user/123',
data: {'id': 123, 'name': 'Test User'},
);
// Act
final user = await fetchUser(123);
// Assert
expect(user.id, equals(123));
expect(user.name, equals('Test User'));
});
test('should handle API errors', () async {
// Arrange
Joker.stubUrl(
host: 'api.example.com',
path: '/user/999',
response: JokerResponse(
statusCode: 404,
body: '{"error": "User not found"}',
),
);
// Act & Assert
expect(() => fetchUser(999), throwsA(isA<UserNotFoundException>()));
});
});
}
URL Matching #
Joker uses flexible URL matching:
- Host matching: Exact string match against the request host
- Path matching: Exact string match against the request path
- Method matching: Case-insensitive HTTP method matching
- Partial matching: You can specify only the parameters you want to match
// Match any request to api.example.com
Joker.stubJson(host: 'api.example.com', data: {'default': true});
// Match only GET requests to /users
Joker.stubJson(path: '/users', method: 'GET', data: {'users': []});
// Match specific host and path combination
Joker.stubJson(
host: 'api.example.com',
path: '/posts/123',
data: {'id': 123, 'title': 'Test Post'},
);
Best Practices #
-
Always call
Joker.stop(): Use try-finally blocks or tearDown methods to ensure normal HTTP behavior is restored. -
Use named stubs for complex scenarios: Named stubs make it easier to manage and remove specific stubs.
-
Clear stubs between tests: Use
Joker.clearStubs()in tearDown to ensure test isolation. -
Leverage one-time stubs: Use
removeAfterUse: truefor scenarios where a stub should only match once. -
Simulate realistic delays: Add delays to test timeout handling and loading states.
// Good practice example
void main() {
group('API Tests', () {
setUp(() => Joker.start());
tearDown(() {
Joker.stop();
Joker.clearStubs();
});
// Your tests here...
});
}
Limitations #
- Only works with HTTP clients that respect
HttpOverrides.global - URL matching is exact string matching (no regex or wildcards yet)
- Cannot intercept HTTPS certificate validation
License #
This project is licensed under the MIT License - see the LICENSE file for details.