Архитектура приложений с картографической составляющей почти всегда включает несколько уровней логики: инициализация карты, управление слоями, работа с объектами (маркер, полигон, линия), взаимодействие с геокодингом и маршрутизацией. Несмотря на то что сама карта является внешним визуальным компонентом, значительная часть бизнес-логики может и должна быть покрыта юнит-тестами.
Ключевая сложность тестирования HERE Maps API заключается в том, что большинство объектов завязаны на браузерный контекст и нативные визуальные рендереры WebGL. Это делает прямое тестирование невозможным без изоляции зависимостей.
Первый шаг к тестируемости — отделение бизнес-логики от прямых вызовов HERE Maps API.
// плохо тестируемый код
const platform = new H.service.Platform({
apikey: 'YOUR_API_KEY'
});
const map = new H.Map(
document.getElementById('map'),
platform.createDefaultLayers().vector.normal.map,
{
center: { lat: 52.5, lng: 13.4 },
zoom: 10
}
);
Такой код невозможно протестировать без браузера и реального DOM.
Правильный подход — создание обёртки над API.
export class MapService {
constructor(mapInstance) {
this.map = mapInstance;
}
setCenter(lat, lng) {
this.map.setCenter({ lat, lng });
}
addMarker(marker) {
this.map.addObject(marker);
}
removeObject(object) {
this.map.removeObject(object);
}
}
Теперь тестируется не HERE Maps, а собственная логика взаимодействия с ним.
Юнит-тесты должны исключать реальные зависимости от HERE Maps SDK.
export function createMockMap() {
return {
setCenter: jest.fn(),
addObject: jest.fn(),
removeObject: jest.fn(),
getObjects: jest.fn(() => [])
};
}
import { MapService } from './MapService';
import { createMockMap } from './mocks/createMockMap';
test('setCenter вызывает метод карты с корректными координатами', () => {
const mockMap = createMockMap();
const service = new MapService(mockMap);
service.setCenter(10, 20);
expect(mockMap.setCenter).toHaveBeenCalledWith({
lat: 10,
lng: 20
});
});
В HERE Maps маркер часто создаётся через H.map.Marker,
но в тестах он заменяется простым объектом.
function createMockMarker() {
return {
setGeometry: jest.fn(),
getGeometry: jest.fn()
};
}
test('addMarker добавляет объект на карту', () => {
const map = createMockMap();
const service = new MapService(map);
const marker = createMockMarker();
service.addMarker(marker);
expect(map.addObject).toHaveBeenCalledWith(marker);
});
Часто карта используется как визуальный слой, а вся логика координат находится отдельно.
export function calculateCenter(points) {
const sum = points.reduce(
(acc, p) => {
acc.lat += p.lat;
acc.lng += p.lng;
return acc;
},
{ lat: 0, lng: 0 }
);
return {
lat: sum.lat / points.length,
lng: sum.lng / points.length
};
}
test('calculateCenter возвращает среднее значение координат', () => {
const points = [
{ lat: 0, lng: 0 },
{ lat: 10, lng: 10 }
];
expect(calculateCenter(points)).toEqual({
lat: 5,
lng: 5
});
});
Такая логика не зависит от HERE Maps и полностью покрывается юнит-тестами.
HERE Geocoding API также не должен вызываться напрямую в юнит-тестах.
export class GeocodingService {
constructor(client) {
this.client = client;
}
async geocode(query) {
const result = await this.client.geocode({ q: query });
return result.items;
}
}
const mockClient = {
geocode: jest.fn()
};
test('geocode возвращает items из ответа API', async () => {
mockClient.geocode.mockResolvedValue({
items: [{ title: 'Test location' }]
});
const service = new GeocodingService(mockClient);
const result = await service.geocode('Berlin');
expect(result).toEqual([{ title: 'Test location' }]);
});
Маршрутизация в HERE Maps часто строится через
H.service.RoutingService.
export class RoutingServiceWrapper {
constructor(routingService) {
this.routingService = routingService;
}
async calculateRoute(start, end) {
return new Promise((resolve, reject) => {
this.routingService.calculateRoute(
{
origin: start,
destination: end,
return: 'polyline'
},
resolve,
reject
);
});
}
}
const mockRoutingService = {
calculateRoute: jest.fn()
};
test('calculateRoute вызывает routingService с правильными параметрами', async () => {
mockRoutingService.calculateRoute.mockImplementation((params, cb) => {
cb({ routes: ['route1'] });
});
const wrapper = new RoutingServiceWrapper(mockRoutingService);
const result = await wrapper.calculateRoute('A', 'B');
expect(result).toEqual({ routes: ['route1'] });
});
Карты часто используют слои: трафик, спутник, пользовательские данные.
export class LayerManager {
constructor(map) {
this.map = map;
this.layers = new Map();
}
addLayer(name, layer) {
this.layers.set(name, layer);
this.map.addLayer(layer);
}
removeLayer(name) {
const layer = this.layers.get(name);
if (layer) {
this.map.removeLayer(layer);
this.layers.delete(name);
}
}
}
test('addLayer добавляет слой и сохраняет его', () => {
const map = {
addLayer: jest.fn(),
removeLayer: jest.fn()
};
const manager = new LayerManager(map);
const layer = {};
manager.addLayer('traffic', layer);
expect(map.addLayer).toHaveBeenCalledWith(layer);
});
Если часть кода всё же использует глобальный H, его
можно замокать.
global.H = {
map: {
Marker: jest.fn()
},
service: {
Platform: jest.fn()
}
};
Для тестов, где требуется DOM-узел карты:
document.body.innerHTML = `
<div id="map"></div>
`;
test('контейнер карты существует', () => {
const container = document.getElementById('map');
expect(container).not.toBeNull();
});
Невозможно тестировать в юнит-тестах, выносится на e2e уровень.
Не покрываются unit-тестами, заменяются mock-слоями.
Проверяются через snapshot/e2e, но не через unit.
Юнит-тесты эффективно покрывают:
Не покрываются: