Подменяй ответы API, перехватывая fetch и XHR
Бэкенд не готов. Или готов, но ты не можешь по команде заставить его вернуть пустой список, ошибку 500 и трёхсекундную задержку. Эта статья показывает, как перехватить fetch и XMLHttpRequest прямо в браузере JS-правилом JustZix, возвращая поддельный JSON, — чтобы ты мог собирать и тестировать фронтенд под любой сценарий.
Зачем вообще перехватывать в браузере
Обычный ответ на «мне нужны фейковые данные» — мок-сервер: MSW, json-server, заглушка в dev-окружении. Это отличные вещи, и для целой команды это правильный выбор. Но иногда хочется чего-то полегче:
- Ты отлаживаешь продакшен и не можешь направить его на фейковый бэкенд.
- Хочешь воспроизвести один конкретный краевой случай, не трогая настройку проекта.
- Ты ревьюишь чужой сайт и доступа к репозиторию у тебя нет вообще.
JS-правило JustZix живёт в браузере, применяется по URL-паттерну и не требует никаких изменений в проекте. В этом его ниша: быстрый, точечный, легко выбрасываемый моккинг.
Оборачиваем window.fetch
Ключевой трюк — заменить window.fetch своей функцией, которая для каждого запроса решает: подменить его или пропустить. Запусти это на document_start, чтобы установка произошла до того, как код приложения вызовет fetch:
// Сохраняем оригинал, чтобы при необходимости уйти в реальную сеть
const realFetch = window.fetch.bind(window);
// Твоя таблица моков: совпадение по подстроке URL -> фабрика ответа
const mocks = [
{
match: '/api/user/profile',
response: () => jsonResponse({ id: 1, name: 'Ada Lovelace', plan: 'pro' })
},
{
match: '/api/orders',
response: () => jsonResponse([]) // краевой случай с пустым списком
}
];
window.fetch = async (input, init) => {
const url = typeof input === 'string' ? input : input.url;
const hit = mocks.find(m => url.includes(m.match));
if (hit) {
console.log('[mock] intercepted', url);
return hit.response();
}
return realFetch(input, init); // не замокано -> реальный запрос
};
Сборка синтетического Response
Твоё приложение ждёт настоящий объект Response — оно вызовет .json(), проверит .ok, прочитает .status. Конструктор Response даёт всё это бесплатно:
// Хелпер: ответ 200 OK с JSON
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { 'Content-Type': 'application/json' }
});
}
Поскольку это настоящий Response, код вида const r = await fetch(...); if (r.ok) return r.json(); работает без изменений. В этом весь смысл — приложение не отличит подделку.
Имитация задержки
Пустые списки и счастливые пути просты. Баги живут на медленном пути: спиннеры, которые никогда не пропадают, гонки, двойные отправки. Добавь задержку перед резолвом:
// Небольшой хелпер для паузы
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// В таблице моков дожидаемся его перед возвратом
{
match: '/api/search',
response: async () => {
await sleep(3000); // 3 секунды «загрузки...»
return jsonResponse({ results: [] });
}
}
Имитация кодов ошибок
Теперь несчастливые пути. 500, 404, 401, который должен выкинуть тебя на логин, — верни их с правильным статусом, чтобы твоя обработка ошибок действительно сработала:
// 500 Internal Server Error
{
match: '/api/checkout',
response: () => jsonResponse(
{ error: 'payment_gateway_timeout' },
500
)
}
// Сетевой сбой (fetch отклоняется, а не резолвится)
{
match: '/api/flaky',
response: () => Promise.reject(new TypeError('Failed to fetch'))
}
Заметь разницу: 500 всё равно резолвится с r.ok === false, а оборванное соединение отклоняется. Тестируй оба — приложения часто обрабатывают одно и падают на другом.
Оборачиваем XMLHttpRequest
Много старого кода и библиотек вроде axios под капотом всё ещё используют XMLHttpRequest. Перехват fetch их не затрагивает. XHR подделывать неуклюже, но тонкая обёртка покрывает типичный случай:
const RealXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function () {
const xhr = new RealXHR();
const realOpen = xhr.open;
let mockedUrl = null;
xhr.open = function (method, url, ...rest) {
if (url.includes('/api/legacy')) mockedUrl = url;
return realOpen.call(this, method, url, ...rest);
};
const realSend = xhr.send;
xhr.send = function (...args) {
if (!mockedUrl) return realSend.apply(this, args);
// Подделываем успешный ответ без обращения к сети
setTimeout(() => {
Object.defineProperty(xhr, 'readyState', { value: 4 });
Object.defineProperty(xhr, 'status', { value: 200 });
Object.defineProperty(xhr, 'responseText', {
value: JSON.stringify({ legacy: true })
});
xhr.onreadystatechange && xhr.onreadystatechange();
xhr.onload && xhr.onload();
}, 0);
};
return xhr;
};
Это намеренно минимально — оно покрывает onload / onreadystatechange с JSON-текстом, а это то, что нужно 90% XHR-кода. Если библиотека проверяет getAllResponseHeaders(), тебе придётся заглушить и это. На этом этапе, честно говоря, бери настоящий мок-сервер.
Когда вместо этого взять настоящий мок-сервер
Перехват в браузере — правильный инструмент для быстрой, ограниченной по области, выбрасываемой работы. Переключайся на MSW или json-server, когда:
- Всей команде нужны одни и те же моки, версионированные в репозитории.
- Тебе нужно сопоставление по телу запроса, stateful-последовательности или запись-и-воспроизведение.
- Твой набор тестов (Playwright, Cypress) гоняет моки в CI.
Правило большого пальца: если мок надо коммитить — используй сервер. Если он должен исчезать при выключении правила — используй JustZix.
Смотри также
- Принудительный выбор варианта A/B-теста — замокай эндпоинт флага, чтобы закрепить ветку.
- Собери наблюдатель цены и наличия — следи за DOM вместо сети.
- Готовые к вставке сниппеты в разделе «Примеры».
Хватит ждать бэкенд. Установи JustZix, помести обёртку fetch в ограниченное JS-правило и собирай фронтенд под каждый сценарий уже сегодня.
Оцени эту статью
Оценок пока нет — оцени первым.