При разработке приложений, использующих HERE Technologies JavaScript API, значительная часть логики оказывается связанной с внешними сервисами: загрузкой тайлов, геокодированием, маршрутизацией, поиском объектов и авторизацией. Это делает прямое выполнение тестов нестабильным из-за сетевых задержек, квот, токенов доступа и изменчивости данных.
Моки и стабы становятся ключевым инструментом изоляции прикладной логики от инфраструктурных зависимостей. В тестовой среде они заменяют реальные HTTP-запросы и объекты карты предсказуемыми заглушками.
В стандартной архитектуре HERE Maps JavaScript API используется объектная модель:
H.service.Platform — инициализация платформыH.Map — отображение картыH.service.GeocodingService — геокодированиеH.service.RoutingService — построение маршрутовH.map.Object и производные — визуальные сущностиКаждый из этих компонентов зависит от внешних данных и сети.
Основные проблемы при тестировании:
Стабы заменяют функции API фиксированным поведением. В контексте HERE Maps они применяются для:
Стаб возвращает заранее подготовленный ответ без логики.
Моки расширяют стабы, добавляя:
Моки применяются для проверки взаимодействия с API, а не только результата.
H.service.PlatformБазовый объект платформы часто подменяется на уровне инициализации.
class PlatformStub {
constructor() {}
getSearchService() {
return {
geocode: (params, success) => {
success({
items: [
{
position: { lat: 51.5074, lng: -0.1278 },
address: { label: "London" }
}
]
});
}
};
}
getRoutingService() {
return {
calculateRoute: (params, success) => {
success({
routes: [
{
sections: [
{
travelSummary: {
duration: 3600,
length: 10000
}
}
]
}
]
});
}
};
}
}
Данный подход позволяет полностью исключить HTTP-слой из тестов.
Геокодирование является одной из наиболее часто используемых функций API. В тестовой среде его стабируют через фиксацию набора координат.
const GeocodingServiceStub = {
geocode: (query, onSuccess) => {
const responses = {
"Berlin": {
items: [
{
title: "Berlin",
position: { lat: 52.52, lng: 13.405 }
}
]
},
"unknown": {
items: []
}
};
onSuccess(responses[query.q] || responses["unknown"]);
}
};
Использование такой схемы позволяет тестировать:
Маршрутизация требует более сложной структуры данных, поскольку возвращает многосекционные маршруты.
const RoutingServiceStub = {
calculateRoute: (params, onSuccess, onError) => {
const route = {
routes: [
{
sections: [
{
polyline: "encoded-polyline",
travelSummary: {
duration: 1800,
length: 5000
}
}
]
}
]
};
onSuccess(route);
}
};
const RoutingServiceErrorStub = {
calculateRoute: (params, onSuccess, onError) => {
onError({
message: "ROUTE_NOT_FOUND"
});
}
};
Такой подход используется для проверки устойчивости UI к ошибкам сервиса.
H.MapОбъект карты часто требует DOM и WebGL, что делает его сложным для unit-тестов.
class MapMock {
constructor(container, options) {
this.container = container;
this.options = options;
this.objects = [];
this.zoomLevel = options.zoom;
}
setCenter(coords) {
this.center = coords;
}
setZoom(zoom) {
this.zoomLevel = zoom;
}
addObject(obj) {
this.objects.push(obj);
}
removeObject(obj) {
this.objects = this.objects.filter(o => o !== obj);
}
}
Такой мок позволяет тестировать:
без рендеринга графики.
Маркерные слои и объекты часто стабируются отдельно.
class MarkerMock {
constructor(coords, options) {
this.coords = coords;
this.options = options;
}
setGeometry(coords) {
this.coords = coords;
}
}
class LayerMock {
constructor() {
this.objects = [];
}
addObject(obj) {
this.objects.push(obj);
}
removeObject(obj) {
this.objects = this.objects.filter(o => o !== obj);
}
}
В тестовых фреймворках стабирование часто реализуется через подмену модулей.
jest.mock("h-maps-sdk", () => {
return {
Platform: class {
getSearchService() {
return {
geocode: (params, cb) =>
cb({
items: [{ position: { lat: 10, lng: 10 } }]
})
};
}
}
};
});
Подобная схема позволяет полностью контролировать поведение SDK.
Sinon применяется для создания spy-объектов и контроля вызовов.
import sinon from "sinon";
const routingService = {
calculateRoute: () => {}
};
const spy = sinon.spy(routingService, "calculateRoute");
routingService.calculateRoute({ from: "A", to: "B" });
console.log(spy.calledOnce);
Использование spy позволяет проверять:
При использовании REST API HERE можно применять MSW для перехвата запросов.
import { rest } from "msw";
export const handlers = [
rest.get("/geocode", (req, res, ctx) => {
return res(
ctx.json({
items: [
{
title: "Mock City",
position: { lat: 0, lng: 0 }
}
]
})
);
})
];
MSW обеспечивает тестирование без изменения бизнес-кода.
Разделение системы на уровни:
Каждый слой получает собственные стабы.
Фиксирование структуры ответов HERE API:
items[]routes[]results[]Любые моки должны строго соответствовать контракту.
Использование стабильных наборов данных:
Это снижает вероятность ложных падений тестов.
Для повышения устойчивости приложения стабами моделируются:
const TimeoutStub = {
geocode: (params, success, error) => {
setTimeout(() => {
error({ message: "TIMEOUT" });
}, 0);
}
};
Карты используют тайлы, которые можно стабировать на уровне URL.
const tileStub = (x, y, z) => {
return `data:image/png;base64,FAKE_TILE_${x}_${y}_${z}`;
};
Такой подход используется для UI-тестов без сетевой нагрузки.
В непрерывной интеграции стабирование обеспечивает:
Обычно конфигурация включает:
API оборачивается в сервис:
class MapService {
constructor(platform) {
this.platform = platform;
}
geocode(query) {
return new Promise(resolve => {
this.platform.getSearchService().geocode(query, resolve);
});
}
}
Это позволяет подменять platform в тестах.
Создание промежуточного слоя между HERE API и приложением:
Изоляция источника данных:
MapRepositoryRouteRepositoryGeoRepositoryКаждый репозиторий получает стабированный транспорт.
Структура обычно включает:
__mocks__/here/stubs/geocoding.jsstubs/routing.jsfixtures/cities.jsonfixtures/routes.jsonФикстуры используются как единый источник данных для всех тестов.
HERE Maps активно использует callback-ориентированные API, что требует имитации асинхронности.
const asyncGeocodeStub = {
geocode: (params, success) => {
Promise.resolve().then(() => {
success({
items: [{ position: { lat: 1, lng: 1 } }]
});
});
}
};
Это позволяет сохранять поведение, близкое к реальному SDK.