Моки и заглушки для API

Работа с внешними API является неотъемлемой частью современного фронтенд-разработки. Для ускорения разработки, тестирования и изоляции компонентов часто используют моки и заглушки. Mithril, как легковесный и быстрый фреймворк, предоставляет гибкие возможности для интеграции с асинхронными источниками данных и легко сочетается с инструментами для создания моков.


Основы мокирования в Mithril

Mithril использует функцию m.request для выполнения HTTP-запросов. Основная идея мокирования заключается в том, чтобы заменить реальные запросы на фиктивные ответы без обращения к серверу. Это позволяет:

  • тестировать UI независимо от доступности API,
  • имитировать различные состояния ответа (успешный ответ, ошибка, задержка),
  • ускорить разработку, не дожидаясь готовности бекенда.

Простейший пример мокирования m.request:

const originalRequest = m.request;

m.request = function(options) {
    if (options.url === '/api/users') {
        return Promise.resolve([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
    }
    return originalRequest(options);
};

Здесь все запросы к /api/users возвращают заранее подготовленный массив объектов, а остальные запросы продолжают работать через реальный m.request.


Использование заглушек для имитации задержек

Для более реалистичного тестирования интерфейсов полезно имитировать сетевые задержки. Можно добавить задержку с помощью setTimeout:

m.request = function(options) {
    if (options.url === '/api/posts') {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve([
                    { id: 1, title: 'Post 1' },
                    { id: 2, title: 'Post 2' }
                ]);
            }, 500); // задержка 500 мс
        });
    }
    return originalRequest(options);
};

Это позволяет тестировать индикаторы загрузки и обработку состояний ожидания в компонентах Mithril.


Моки для разных состояний ответа

Для полноценного тестирования компонентов важно проверять, как UI реагирует на разные сценарии API:

  1. Успешный ответ — возвращает данные:
return Promise.resolve({ status: 'ok', data: [...] });
  1. Ошибка сервера — имитируется через Promise.reject:
return Promise.reject({ status: 500, message: 'Internal Server Error' });
  1. Пустой результат — полезно для проверки компонентов без данных:
return Promise.resolve([]);

Комбинируя эти подходы, можно полностью моделировать поведение серверной части.


Организация моков в проекте

Для крупных проектов рекомендуется структурировать моки отдельно, чтобы не смешивать их с рабочим кодом. Например:

/src
  /mocks
    api.js

Файл api.js может содержать объект с методами-заглушками:

export const mockApi = {
    getUsers: () => Promise.resolve([{ id: 1, name: 'Alice' }]),
    getPosts: () => Promise.resolve([]),
    createUser: (user) => Promise.resolve({ ...user, id: Date.now() })
};

Подключение мока в компонент:

import { mockApi } from './mocks/api';

m.mount(document.body, {
    oninit: vnode => {
        mockApi.getUsers().then(users => vnode.state.users = users);
    },
    view: vnode => m('ul', vnode.state.users.map(u => m('li', u.name)))
});

Такой подход делает код чистым и легко управляемым.


Интеграция с тестами

Mithril хорошо сочетается с тестовыми библиотеками (например, Mocha, Jest). Использование моков для m.request позволяет писать юнит-тесты для компонентов без необходимости реального сервера:

it('должен отображать список пользователей', async () => {
    m.request = () => Promise.resolve([{ id: 1, name: 'Alice' }]);
    const vnode = m.mount(document.createElement('div'), UserListComponent);
    await m.redraw(); // ждать обновления
    const listItems = vnode.dom.querySelectorAll('li');
    assert.equal(listItems.length, 1);
    assert.equal(listItems[0].textContent, 'Alice');
});

Таким образом, тестирование становится быстрым, детерминированным и полностью изолированным от бекенда.


Комбинирование моков и реальных API

Иногда нужно смешанное использование: часть запросов мокируются, часть идет на реальный сервер. Для этого можно использовать проверку URL и выбирать стратегию:

m.request = function(options) {
    if (options.url.startsWith('/api/mock/')) {
        return Promise.resolve([{ id: 1, name: 'Mocked' }]);
    } else {
        return originalRequest(options);
    }
};

Это полезно для постепенной интеграции с реальным API без ломки существующей логики.


Полезные рекомендации

  • Всегда хранить моки отдельно, чтобы их можно было легко отключать.
  • Использовать промисы для имитации асинхронного поведения.
  • Моделировать все возможные состояния ответа (успех, ошибка, пустой результат).
  • Проверять совместимость моков с жизненным циклом компонентов Mithril (oninit, oncreate, onupdate).
  • Для сложных тестов можно подключать библиотеки вроде fetch-mock или sinon, адаптируя их под m.request.

Моки и заглушки являются мощным инструментом для ускорения разработки, упрощения тестирования и обеспечения стабильного поведения компонентов Mithril даже при нестабильной работе реального API.