Работа с Mapbox GL JS в контексте unit-тестов требует учёта специфики WebGL, асинхронной загрузки стилей и сложной внутренней модели состояния карты. Классические подходы к тестированию DOM-логики оказываются недостаточными, поскольку значительная часть поведения библиотеки связана с графическим рендерингом и взаимодействием с GPU.
Основная сложность unit-тестирования связана с тем, что карта:
Поэтому прямое создание экземпляра карты в тестовой среде без подготовки приводит к ошибкам и нестабильным тестам.
Ключевое ограничение:
Mapbox GL JS не предназначен для выполнения в Node.js без эмуляции WebGL.
Для unit-тестирования обычно используется связка:
Базовая настройка Jest:
module.exports = {
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/test/setup.js"]
};
Без WebGL карта не инициализируется. Для этого применяется
headless-gl:
const { createCanvas } = require("canvas");
const gl = require("gl");
HTMLCanvasElement.prototype.getContext = function (type) {
if (type === "webgl" || type === "experimental-webgl") {
return gl(256, 256);
}
return null;
};
Такой подход позволяет пройти этап инициализации рендера, но не гарантирует корректность визуального результата.
В unit-тестах часто используется частичная или полная подмена API карты.
Пример минимального мок-объекта:
class MapMock {
constructor(options) {
this.options = options;
this.events = {};
}
on(event, handler) {
this.events[event] = handler;
}
off() {}
addLayer(layer) {
this.layer = layer;
}
getSource(id) {
return { id };
}
remove() {}
}
module.exports = { Map: MapMock };
Такой подход позволяет тестировать бизнес-логику, не затрагивая WebGL-слой.
Unit-тесты часто проверяют корректность передачи конфигурации:
import { Map } from "mapbox-gl";
test("инициализация карты с корректными параметрами", () => {
const map = new Map({
container: "map",
style: "mapbox://styles/example/style",
center: [30, 50],
zoom: 10
});
expect(map.options.zoom).toBe(10);
expect(map.options.center).toEqual([30, 50]);
});
Здесь тестируется не рендеринг, а корректность конфигурации.
Работа со слоями является частой частью логики:
test("добавление слоя сохраняется в состоянии карты", () => {
const map = new MapMock();
map.addLayer({
id: "points",
type: "circle",
source: "points-source"
});
expect(map.layer.id).toBe("points");
});
В реальном Mapbox GL JS добавление слоя инициирует пересчёт рендера, но в unit-тестах это заменяется проверкой состояния.
Источники данных являются фундаментальной частью архитектуры:
test("getSource возвращает источник по id", () => {
const map = new MapMock();
const source = map.getSource("cities");
expect(source.id).toBe("cities");
});
При необходимости мок может расширяться для имитации GeoJSON, vector tiles и raster sources.
Mapbox GL JS активно использует события:
loadrenderclickmoveТестирование событий обычно сводится к проверке регистрации и вызова обработчиков:
test("обработчик события load вызывается", () => {
const map = new MapMock();
const handler = jest.fn();
map.on("load", handler);
map.events.load();
expect(handler).toHaveBeenCalled();
});
Стиль карты загружается асинхронно, что требует имитации Promise-логики:
function loadStyle(map, style) {
return new Promise((resolve) => {
map.setStyle(style);
resolve(style);
});
}
test("загрузка стиля завершается успешно", async () => {
const map = new MapMock();
const style = await loadStyle(map, "dark");
expect(style).toBe("dark");
});
Snapshot-тесты полезны для фиксации структуры конфигурации:
test("конфигурация карты соответствует ожиданию", () => {
const config = {
style: "streets",
zoom: 12,
center: [40, 55]
};
expect(config).toMatchSnapshot();
});
При работе с Mapbox GL JS важно разграничивать уровни:
Unit-тесты не должны зависеть от рендера карты или сетевых запросов.
Загрузка стилей и тайлов часто требует подмены fetch:
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ version: 8 })
})
);
Это позволяет тестировать поведение карты без обращения к внешним сервисам.
Часто Mapbox GL JS используется как визуальный слой поверх данных. В таких случаях unit-тесты концентрируются на преобразовании данных:
function transformPoints(points) {
return points.map(p => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [p.lng, p.lat]
}
}));
}
test("преобразование точек в GeoJSON", () => {
const result = transformPoints([{ lat: 10, lng: 20 }]);
expect(result[0].geometry.coordinates).toEqual([20, 10]);
});
Некоторые аспекты невозможно корректно покрыть unit-тестами:
Эти области требуют интеграционного или визуального тестирования.
Для снижения хрупкости тестов применяется стратегия абстракции:
new Map()Такой подход позволяет тестировать бизнес-логику независимо от специфики рендера и ограничений WebGL.