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.
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
- 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 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
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 are defined within individual test files and take precedence over default mocks.
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
},
);
});
});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
},
);The tests/api-mocking/mockHelpers.ts file provides several utilities for mocking:
For simple GET/POST/PUT/DELETE requests:
await setupMockRequest(mockServer, {
requestMethod: 'GET',
url: 'https://api.example.com/data',
response: { data: [] },
responseCode: 200,
});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.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
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.
If multiple tests need the same API mocked, add it to the appropriate default mock file rather than duplicating it in each test.
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';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
},
);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 ErrorFor 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
},
);MetaMask Mobile uses remote feature flags to control features dynamically. The E2E framework provides specialized helpers for mocking these flags.
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')
);
};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),
);
};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
});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- 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.
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)
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"- 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