useMockClient function

void useMockClient({
  1. bool shouldFail = false,
  2. int failStatusCode = 400,
  3. Object? responseFactory(
    1. Request request
    )?,
  4. MockResponder? responder,
})

Configure all RestAPI instances to use an in-memory mock client.

Built on the public setClientFactory seam, so nothing in the core library references test scaffolding.

Call in test setup:

setUp(() => useMockClient());                              // 200 + fake data
setUp(() => useMockClient(shouldFail: true, failStatusCode: 500));

A bare call restores generic successful fake responses and clears any prior responder; every other call replaces it with the explicitly configured per-request behavior.

Also usable for running the app against mock data in development:

// bootstrap.dart — Environment.useMocks is a compile-time constant, so
// this branch is tree-shaken out of release builds entirely.
if (Environment.useMocks) useMockClient();

By default every request resolves to FakeUtils.fakeJson, which is generic and fine for exercising status handling. Supply responseFactory to return app-shaped data keyed off the request:

useMockClient(responseFactory: (request) => switch (request.url.path) {
  '/api/v1/products' => [ProductFake.fake().toJson(), ProductFake.fake().toJson()],
  _ => ProductFake.fake().toJson(),
});

responseFactory can only ever produce a 200. When a test needs to vary the status, set headers, or change its answer partway through, supply responder — or assign bifrostMockResponder at any point afterwards:

useMockClient(responder: (request) async => http.Response('[]', 200));
// ...later, in one test:
bifrostMockResponder = (request) async => http.Response('denied', 403);

Responses are stamped with their request. MockClient populates response.request from the response the handler returns, so it is null unless the handler sets it — and clients that dereference response.request! while parsing (PostgREST does, on both its success and error paths) throw a null-check error instead of returning data. Since that failure surfaces far from its cause, this fills the field in whenever a responder left it empty.

Implementation

void useMockClient({
  bool shouldFail = false,
  int failStatusCode = 400,
  Object? Function(http.Request request)? responseFactory,
  MockResponder? responder,
}) {
  final modes = <bool>[
    shouldFail,
    responseFactory != null,
    responder != null,
  ].where((configured) => configured).length;
  if (modes > 1) {
    throw ArgumentError(
      'Configure only one of shouldFail, responseFactory, or responder.',
    );
  }

  if (responder != null) {
    bifrostMockResponder = responder;
  } else if (shouldFail || responseFactory != null) {
    bifrostMockResponder = (request) => _defaultResponse(
          request,
          shouldFail: shouldFail,
          failStatusCode: failStatusCode,
          responseFactory: responseFactory,
        );
  } else {
    bifrostMockResponder = null;
  }

  setClientFactory(
    () => MockClient((request) async {
      // Read the global per request rather than closing over `responder`, so a
      // later assignment reaches a client an SDK has already captured.
      final current = bifrostMockResponder;
      final response = current != null
          ? await current(request)
          : _defaultResponse(
              request,
              shouldFail: false,
              failStatusCode: 400,
            );

      return response.request == null
          ? _withRequest(response, request)
          : response;
    }),
  );
}