ion_test

In-process testing utilities for ion_web handlers.

Usage

import 'package:ion_extra/ion_extra.dart';
import 'package:ion_router/ion_router.dart';
import 'package:ion_test/ion_test.dart';
import 'package:ion_web/ion_web.dart';

void main() {
  final app = Router()
    ..get('/ping', (req) => RawJson({'pong': true}))
    ..post('/echo', (req) async => Response.text('echo: ${await req.text()}'))
    ..get('/sse', (req) => Response.sse(() async* {
      yield const SseEvent.text('Hello', event: 'greeting');
      yield const SseEvent.json({'status': 'ok'});
    }));

  ionTest(
    'GET /ping returns JSON pong',
    build: () => app,
    act: (c) => c.get('/ping'),
    expect: () => RawJson({'pong': true}),
  );

  ionTest(
    'POST /echo returns echoed text',
    build: () => app,
    act: (c) => c.post('/echo', body: .text('hello')),
    expect: () => Response.text('echo: hello'),
  );

  ionTest(
    'GET /sse streams SSE events',
    build: () => app,
    act: (c) => c.get('/sse'),
    expect: () => Response.sse(() async* {
      yield const SseEvent.text('Hello', event: 'greeting');
      yield const SseEvent.json({'status': 'ok'});
    }),
  );
}

Custom Response Comparators

Register custom comparators globally or per test:

// Register a comparator by Response subclass (e.g. XmlResponse):
registerResponseComparator(
  ResponseComparator.byType<XmlResponse>(
    compare: (actual, expected) async {
      final actualXml = XmlDocument.parse(utf8.decode(await actual.readBytes()));
      final expectedXml = XmlDocument.parse(utf8.decode(await expected.readBytes()));

      final actualFormatted = actualXml.toXmlString(pretty: true);
      final expectedFormatted = expectedXml.toXmlString(pretty: true);

      try {
        expect(actualFormatted, equals(expectedFormatted));
      } on TestFailure catch (e) {
        final diffText = formatDiff(expected: expectedFormatted, actual: actualFormatted);
        throw TestFailure('${e.message}\n$diffText');
      }
    },
  ),
);

// Register a comparator by Content-Type (e.g. Protobuf payload decoding):
registerResponseComparator(
  ResponseComparator.byContentType(
    'application/x-protobuf',
    compare: (actual, expected) async {
      final actualProto = UserProto.fromBuffer(await actual.readBytes());
      final expectedProto = UserProto.fromBuffer(await expected.readBytes());
      expect(actualProto, equals(expectedProto));
    },
  ),
);

// Or pass locally to ionTest:
ionTest(
  'GET /user/1 returns XML user',
  build: () => app.call,
  act: (c) => c.get('/user/1'),
  expect: () => XmlResponse(UserXmlDto(id: 1, name: 'Alice')),
  comparators: [xmlResponseComparator],
);

Libraries

ion_test