Handler Providers
server_testing does not know how to boot your framework directly—it depends on a RequestHandler abstraction. A provider can implement the interface directly when it owns a server, or extend RequestHandlerBase when it is in-memory only.
abstract class RequestHandlerBase implements RequestHandler {
Future<void> handleRequest(HttpRequest request);
// startServer() throws UnsupportedError by default.
// close() is a no-op by default.
}
handleRequest is used for the in-memory transport, while startServer/close back the ephemeral server transport.
Using the Built-in IoRequestHandler
For applications using dart:io HttpServer directly, server_testing ships with a default handler:
import 'dart:io';
import 'package:server_testing/server_testing.dart';
// Your existing request handler function
Future<void> handleRequest(HttpRequest request) async {
final response = request.response;
if (request.uri.path == '/ping') {
response.statusCode = 200;
response.write('pong');
} else {
response.statusCode = 404;
response.write('Not found');
}
await response.close();
}
void main() {
// Wrap your handler with IoRequestHandler
final handler = IoRequestHandler(handleRequest);
serverTest('GET /ping returns pong', (client, _) async {
final response = await client.get('/ping');
response.assertStatus(200).assertBodyEquals('pong');
}, handler: handler);
}
IoRequestHandler works with any FutureOr<void> Function(HttpRequest) callback, making it the simplest way to test applications that don't use a web framework.
Using the Shelf Provider
import 'package:shelf/shelf.dart' as shelf;
import 'package:server_testing/server_testing.dart';
import 'package:server_testing_shelf/server_testing_shelf.dart';
final app = (shelf.Request req) async => shelf.Response.ok('ok');
final handler = ShelfRequestHandler(app);
serverTest('works', (client, _) async {
final res = await client.get('/');
res.assertStatus(200).assertBody('ok');
}, handler: handler);
server_testing_shelf handles translating a Shelf Response into an HttpResponse, manages the server lifecycle, and includes helpers for middleware chains.
Authoring a New Provider
When adapting another framework:
- Translate the incoming
HttpRequestinto whatever request object your framework expects. - Invoke the framework and await the response.
- Write the response back to the
HttpResponseattached to the original request. - Manage server lifecycle in
startServer/close. Reuse existing bootstrapping logic if your framework already knows how to bind a port. - Keep translation utilities isolated—write separate tests for them using the Mockito helpers from
server_testing/mock.dart.
Template
class MyFrameworkRequestHandler implements RequestHandler {
final MyFrameworkApp app;
HttpServer? _server;
MyFrameworkRequestHandler(this.app);
Future<void> handleRequest(HttpRequest request) async {
final fwReq = await _toFrameworkRequest(request);
final fwRes = await app.handle(fwReq);
await _writeFrameworkResponse(request.response, fwRes);
}
Future<int> startServer({int port = 0}) async {
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
_server!.listen(handleRequest);
return _server!.port;
}
Future<void> close([bool force = true]) async {
await _server?.close(force: force);
_server = null;
}
}
Best Practices
- Keep handlers stateless when possible. If you must share state (e.g. database connections), clean up in
close()so each test starts fresh. - Validate both transports. Unit-test your provider by calling
handleRequestwith mock objects, then add higher-level tests usingTransportMode.ephemeralServerto verify real socket behaviour. - Expose factory helpers. Consider shipping your handler provider in its own package (like
server_testing_shelf) so applications can depend on a stable adapter. - Reuse in browser tests. The same handler instance can serve an HTTP client and a WebDriver-driven browser by sharing
baseUrlFuture.