E2E API Mocking Guide
January 28, 2026 · View on GitHub
This guide explains how to mock APIs in MetaMask Mobile E2E tests. The mocking system allows tests to run predictably by intercepting HTTP requests and returning controlled responses.
Architecture Overview
The E2E mocking system consists of three main components:
- Default Mocks (
tests/api-mocking/mock-responses/defaults/) - Shared mocks used across all tests - Test-Specific Mocks - Custom mocks defined within individual test files
How Mocking Works
- All network requests go through a proxy server that can intercept and mock responses
- Default mocks are automatically loaded for all tests via
FixtureHelper.createMockAPIServer() -> startMockSerer() - Test-specific mocks take precedence over default mocks
- The mock server runs on a dedicated port and is automatically started/stopped by the test framework
Default Mocks
Default mocks are organized by API category in tests/api-mocking/mock-responses/defaults/:
defaults/
├── index.ts # Aggregates all default mocks
├── accounts.ts # Account-related API mocks
├── defi-adapter.ts # DeFi protocol mocks
├── dapp-scanning.ts # Dapp security scanning mocks
├── metametrics-test.ts # Analytics mocks for testing
├── onramp-apis.ts # Onramp service mocks
├── price-apis.ts # Price feed mocks
├── staking.ts # Staking API mocks
├── swap-apis.ts # Swap/exchange API mocks
├── token-apis.ts # Token metadata API mocks
├── user-storage.ts # User storage service mocks
├── walletconnect.ts # WalletConnect mocks
└── web-3-auth.ts # Web3 authentication mocks
Adding New Default Mocks
To add default mocks that all tests can benefit from:
- Create or edit a category file in
defaults/folder:
// defaults/my-new-service.ts
import { MockApiEndpoint } from '../../framework/types';
export const MY_SERVICE_MOCKS = {
GET: [
{
urlEndpoint: 'https://api.myservice.com/data',
responseCode: 200,
response: { success: true, data: [] },
},
] as MockApiEndpoint[],
POST: [
{
urlEndpoint: 'https://api.myservice.com/submit',
responseCode: 201,
response: { id: '123', status: 'created' },
},
] as MockApiEndpoint[],
};
- Add to the main index file:
// defaults/index.ts
import { MY_SERVICE_MOCKS } from './my-new-service';
export const DEFAULT_MOCKS = {
GET: [
// ... existing mocks
...(MY_SERVICE_MOCKS.GET || []),
],
POST: [
// ... existing mocks
...(MY_SERVICE_MOCKS.POST || []),
],
// ... other methods
};
Default mocks are automatically included in all tests through FixtureHelper.createMockAPIServer().
Test-Specific Mocks
Test-specific mocks are defined within individual test files and take precedence over default mocks.
Method 1: Using testSpecificMock Parameter
Pass a testSpecificMock function to withFixtures:
import { withFixtures } from '../framework/fixtures/FixtureHelper';
import { setupMockRequest } from '../api-mocking/mockHelpers';
describe('My Test Suite', () => {
it('should handle custom API response', async () => {
const testSpecificMock = async (mockServer: Mockttp) => {
await setupMockRequest(mockServer, {
requestMethod: 'GET',
url: 'https://api.example.com/custom',
response: { customData: 'test' },
responseCode: 200,
});
};
await withFixtures(
{
fixture: new FixtureBuilder().build(),
testSpecificMock,
},
async ({ mockServer }) => {
// Your test code here
},
);
});
});
Method 2: Using Mock Server Reference
Access the mock server directly within the test:
await withFixtures(
{
fixture: new FixtureBuilder().build(),
},
async ({ mockServer }) => {
// Set up additional mocks within the test
await setupMockRequest(mockServer, {
requestMethod: 'POST',
url: 'https://api.example.com/submit',
response: { result: 'success' },
responseCode: 201,
});
// Your test code here
},
);
Mock Helper Functions
The tests/api-mocking/mockHelpers.ts file provides several utilities for mocking:
setupMockRequest
For simple GET/POST/PUT/DELETE requests:
await setupMockRequest(mockServer, {
requestMethod: 'GET',
url: 'https://api.example.com/data',
response: { data: [] },
responseCode: 200,
});
setupMockPostRequest
For POST requests with body validation:
await setupMockPostRequest(
mockServer,
'https://api.example.com/validate',
{ field: 'expectedValue' }, // Expected request body
{ result: 'validated' }, // Response
{
statusCode: 200,
ignoreFields: ['timestamp'], // Fields to ignore in body validation
},
);
FixtureHelper Integration
Automatic Mock Server Setup
FixtureHelper.createMockAPIServer() automatically:
- Starts a mock server on the configured port
- Loads all default mocks from
defaults/index.ts - Applies test-specific mocks (if provided)
- Calls
mockNotificationServices()for notification-related mocks - Calls
setupRemoteFeatureFlagsMock()for default remote feature flags mocks
Notification Services Mocking
The mockNotificationServices() function in FixtureHelper.ts automatically sets up mocks for:
- Push notification APIs
- Notification list/read/update endpoints
- Feature announcement APIs
- Authentication services for notifications
This is applied to all tests automatically, so no additional setup is needed for basic notification functionality.
Best Practices
1. Use Default Mocks for Common APIs
If multiple tests need the same API mocked, add it to the appropriate default mock file rather than duplicating it in each test.
2. Be Specific with URL Matching
Use specific URL patterns to avoid unintended matches:
// Good - specific endpoint
urlEndpoint: 'https://api.metamask.io/prices/eth';
// Better - use regex for dynamic parts
urlEndpoint: /^https:\/\/api\.metamask\.io\/prices\/[a-z]+$/;
// Avoid - too broad
urlEndpoint: 'metamask.io';
3. Handle POST Request Bodies Properly
For POST requests, validate the request body when needed:
await setupMockPostRequest(
mockServer,
'https://api.example.com/submit',
{
method: 'transfer',
amount: '1000000000000000000', // Expected request body
},
{ success: true },
{
ignoreFields: ['timestamp', 'nonce'], // Ignore dynamic fields
},
);
4. Use Descriptive Response Codes
Always specify appropriate HTTP response codes:
// Success cases
responseCode: 200, // OK
responseCode: 201, // Created
responseCode: 204, // No Content
// Error cases
responseCode: 400, // Bad Request
responseCode: 404, // Not Found
responseCode: 500, // Internal Server Error
5. Organize Test-Specific Mocks
For complex tests with many mocks, organize them in a setup function:
const setupTestMocks = async (mockServer: Mockttp) => {
// Price API mocks
await setupMockRequest(mockServer, {
/* ... */
});
// Token API mocks
await setupMockRequest(mockServer, {
/* ... */
});
// Swap API mocks
await setupMockRequest(mockServer, {
/* ... */
});
};
// Use in test
await withFixtures(
{
fixture: new FixtureBuilder().build(),
testSpecificMock: setupTestMocks,
},
async ({ mockServer }) => {
// Test code
},
);
Remote Feature Flags Mocking
MetaMask Mobile uses remote feature flags to control features dynamically. The E2E framework provides specialized helpers for mocking these flags.
setupRemoteFeatureFlagsMock Helper
Use setupRemoteFeatureFlagsMock from tests/api-mocking/helpers/remoteFeatureFlagsHelper to mock feature flags:
import { setupRemoteFeatureFlagsMock } from '../../api-mocking/helpers/remoteFeatureFlagsHelper';
const testSpecificMock = async (mockServer: Mockttp) => {
await setupRemoteFeatureFlagsMock(
mockServer,
{ rewards: true },
'main', // distribution (optional, defaults to 'main')
);
};
Predefined Feature Flag Mocks
Common feature flag configurations are available in tests/api-mocking/mock-responses/feature-flags-mocks.ts:
import { confirmationsRedesignedFeatureFlags } from '../../api-mocking/mock-responses/feature-flags-mocks';
// For redesigned confirmations UI
const testSpecificMock = async (mockServer: Mockttp) => {
await setupRemoteFeatureFlagsMock(
mockServer,
Object.assign({}, ...confirmationsRedesignedFeatureFlags),
);
};
Feature Flag Override Patterns
The helper supports both simple and nested flag overrides:
// Simple boolean flags
await setupRemoteFeatureFlagsMock(mockServer, {
rewards: true,
carouselBanners: false,
});
// Nested object flags (deep merge support)
await setupRemoteFeatureFlagsMock(mockServer, {
bridgeConfig: {
support: true,
refreshRate: 5000,
},
});
// Combining predefined configs with overrides
await setupRemoteFeatureFlagsMock(mockServer, {
...Object.assign({}, ...confirmationsRedesignedFeatureFlags),
rewards: true, // Override specific flags
});
Distribution and Environment Support
Feature flags can vary by distribution (main/flask) and environment (dev/prod):
// Flask distribution with custom flags
await setupRemoteFeatureFlagsMock(mockServer, { perpsEnabled: true }, 'flask');
// The helper automatically handles both dev and prod environment URLs
Best Practices for Feature Flags
- Use Object.assign with arrays: When combining multiple feature flag arrays, use
Object.assign({}, ...arrays)to properly merge objects:
// Correct - properly merges flag objects
Object.assign({}, ...confirmationsRedesignedFeatureFlags)
// Incorrect - spreads array items directly
...confirmationsRedesignedFeatureFlags
-
Prefer predefined configurations: Use existing configurations from
feature-flags-mocks.tswhen possible rather than defining flags inline. -
Test both flag states: Create tests for both enabled and disabled feature flag states when the feature behavior differs significantly.
-
Use descriptive test names: Include feature flag state in test descriptions when relevant.
Debugging Mocks
Live Request Validation
The mock server tracks requests that weren't mocked and logs them at the end of tests. Check the test output for warnings about unmocked requests. (This will soon be enforced to track new untracked request)
Enable Debug Logging
Add debug logging to see which mocks are being hit:
// The mock helpers automatically log when mocks are triggered
// Check test output for lines like:
// "Mocking GET request to: https://api.example.com/data"
Common Issues
- Mock not triggering: Check URL pattern matching
- Wrong response: Verify mock takes precedence (test-specific > default)
- POST body validation failing: Check
ignoreFieldsand expected request body format - Feature flags not applying: Ensure you're using
Object.assign({}, ...arrays)when combining flag arrays