Skip to content

Latest commit

 

History

History
143 lines (106 loc) · 5.39 KB

File metadata and controls

143 lines (106 loc) · 5.39 KB

API & Feature Flag Mocking — Reference

How Mocking Works

All E2E tests run with a proxy mock server. Requests not matched by a mock reach the real network, but the test framework warns you (and will soon enforce this). Always mock external APIs your feature calls.

testSpecificMock Pattern

Pass to withFixtures to apply mocks only for that test:

import { Mockttp } from 'mockttp';
import { setupRemoteFeatureFlagsMock } from '../../api-mocking/helpers/remoteFeatureFlagsHelper';
import { setupMockRequest } from '../../api-mocking/mockHelpers';

const testSpecificMock = async (mockServer: Mockttp) => {
  // Feature flags
  await setupRemoteFeatureFlagsMock(mockServer, { myFeatureEnabled: true });

  // GET request
  await setupMockRequest(mockServer, {
    requestMethod: 'GET',
    url: 'https://api.example.com/data',
    response: { items: [] },
    responseCode: 200,
  });
};

await withFixtures({ fixture: ..., testSpecificMock }, async () => { ... });

Feature Flag Mocking

import { setupRemoteFeatureFlagsMock } from '../../api-mocking/helpers/remoteFeatureFlagsHelper';

// Simple boolean flags
await setupRemoteFeatureFlagsMock(mockServer, {
  predictTradingEnabled: true,
  carouselBanners: false,
});

// Nested flags
await setupRemoteFeatureFlagsMock(mockServer, {
  bridgeConfig: { support: true, refreshRate: 5000 },
});

// Flask distribution
await setupRemoteFeatureFlagsMock(mockServer, { perpsEnabled: true }, 'flask');

// Combine predefined configs
import { confirmationsRedesignedFeatureFlags } from '../../api-mocking/mock-responses/feature-flags-mocks';
await setupRemoteFeatureFlagsMock(
  mockServer,
  Object.assign({}, ...confirmationsRedesignedFeatureFlags, { myFlag: true }),
);

HTTP Request Mocking

import {
  setupMockRequest,
  setupMockPostRequest,
} from '../../api-mocking/mockHelpers';

// GET
await setupMockRequest(mockServer, {
  requestMethod: 'GET',
  url: 'https://api.example.com/resource',
  response: { data: [] },
  responseCode: 200,
});

// POST with body validation
await setupMockPostRequest(
  mockServer,
  'https://api.example.com/submit',
  { amount: '1000000000000000000' }, // expected request body
  { success: true }, // response
  { statusCode: 201, ignoreFields: ['timestamp', 'nonce'] },
);

Shared Mock Response Files

For mocks used across multiple tests, create a file in tests/api-mocking/mock-responses/:

// tests/api-mocking/mock-responses/predict-mocks.ts
import { MockApiEndpoint } from '../framework/types';

export const PREDICT_MOCKS = {
  GET: [
    {
      urlEndpoint: 'https://predict.api.metamask.io/markets',
      responseCode: 200,
      response: { markets: [{ id: 'btc-usd', name: 'BTC above $100k?' }] },
    },
  ] as MockApiEndpoint[],
};

Then pass directly to withFixtures:

await withFixtures({ fixture: ..., testSpecificMock: PREDICT_MOCKS }, async () => { ... });

Controller-Level Mocking (Advanced)

Only needed when the feature uses SDKs with complex transport (WebSockets, custom protocols) that can't be intercepted at the HTTP level.

  • Implement a mixin in tests/controller-mocking/mock-config/
  • See tests/docs/CONTROLLER_MOCKING.md for details
  • Prefer HTTP-level mocking whenever possible

Debugging Unmocked Requests

Check test output for warnings like:

⚠️  Unmocked request: GET https://api.example.com/resource

Add a mock for every such request to ensure test determinism.

Features using WebSockets or complex transport

Some features depend on WebSockets or other non-HTTP transport (e.g. Perps/HyperLiquid, real-time data). The HTTP mock server cannot intercept these. The repo uses three patterns:

  1. WebSocket mocking — A LocalWebSocketServer (tests/websocket/server.ts) intercepts production WebSocket connections via URL rewriting in the E2E shim. Protocol-specific mocks handle subscribe/unsubscribe and push notifications. See tests/docs/WEBSOCKET_MOCKING.md for full usage guide and how to add new services. Example: tests/websocket/account-activity-mocks.ts for AccountActivity.
  2. Controller-level mocking — A mixin under tests/controller-mocking/mock-config/ replaces provider SDK touchpoints so E2E runs with stable, test-controlled data. Example: perps-controller-mixin.ts for HyperLiquid. See tests/docs/CONTROLLER_MOCKING.md for when and how to use it.
  3. Command queue / test server — Tests that need to drive the app (e.g. inject state or commands) can use CommandQueueServer (tests/framework/fixtures/CommandQueueServer.ts). Enable it in the fixture with useCommandQueueServer: true. Used by Perps specs (e.g. tests/smoke/perps/perps-add-funds.spec.ts, tests/regression/perps/perps-limit-long-fill.spec.ts). The app consumes the queue in E2E context.

When adding support for a new feature that uses WebSockets or similar:

  • For WebSocket services: add a service config in tests/websocket/constants.ts and a protocol mock. See tests/docs/WEBSOCKET_MOCKING.md.
  • For SDK-level mocking: implement under tests/controller-mocking/mock-config/.
  • For test-driven commands: extend the command-queue protocol as needed.
  • Add or update tests/specs that cover the mock infrastructure and the E2E flow.

Prefer HTTP mocking whenever the feature’s API is plain HTTP; use WebSocket mocking for WS connections; use controller mocking or the command server only when necessary.