Архитектура Kepler.gl построена вокруг связки React + Redux, где значительная часть логики вынесена в чистые редьюсеры и селекторы. Это делает библиотеку удобной для юнит-тестирования, поскольку основная бизнес-логика отделена от UI и внешних зависимостей вроде WebGL и Mapbox.
Тестирование в таком проекте обычно делится на несколько уровней:
Ключевой принцип — максимальная изоляция логики от графического слоя карты.
Для юнит-тестирования Kepler.gl чаще всего используется связка:
mapbox-gl, deck.gl,
react-map-glОсобенность Kepler.gl — невозможность запускать реальные WebGL контексты в unit-тестах, поэтому любые визуальные зависимости заменяются заглушками.
Пример базовой настройки моков:
jest.mock('mapbox-gl', () => ({
Map: () => ({
on: jest.fn(),
remove: jest.fn(),
addControl: jest.fn()
}),
NavigationControl: jest.fn()
}));
Также часто мокируется deck.gl:
jest.mock('@deck.gl/react', () => ({
DeckGL: () => null
}));
Такая стратегия позволяет тестировать только логику, не затрагивая рендеринг WebGL сцены.
Редьюсеры — наиболее стабильная часть Kepler.gl, и именно они покрываются юнит-тестами в первую очередь.
Типичный редьюсер отвечает за:
Пример теста редьюсера загрузки данных:
import keplerGlReducer from 'kepler.gl/reducers';
import {actionTypes} from 'kepler.gl/actions';
test('should handle ADD_DATASET', () => {
const initialState = undefined;
const action = {
type: actionTypes.ADD_DATASET,
payload: {
datasets: [{
data: [{lat: 1, lng: 2}],
info: {id: 'test'}
}]
}
};
const state = keplerGlReducer(initialState, action);
expect(state.datasets).toHaveProperty('test');
});
Редьюсеры Kepler.gl часто вложенные, поэтому важно проверять не только верхний уровень состояния, но и глубоко вложенные структуры.
Селекторы в Kepler.gl используются для вычисления производных данных: подготовка данных для визуализации, фильтрация, агрегации.
Они особенно важны, поскольку напрямую влияют на производительность и корректность отображения карты.
Пример селектора:
import {filteredLayerDataSelector} from 'kepler.gl/selectors';
test('should return filtered data based on time filter', () => {
const state = {
keplerGl: {
map: {
datasets: {
test: {
data: [
{time: 1},
{time: 10}
]
}
},
filters: [{
field: 'time',
value: [0, 5]
}]
}
}
};
const result = filteredLayerDataSelector(state);
expect(result.test.data.length).toBe(1);
});
Селекторы часто мемоизированы, поэтому в тестах важно учитывать:
Экшены в Kepler.gl могут быть как синхронными, так и асинхронными (например, загрузка данных, обработка файлов, взаимодействие с UI состоянием).
Тестирование экшенов обычно включает проверку:
Пример теста экшена:
import {addDataToMap} from 'kepler.gl/actions';
test('addDataToMap should create correct action', () => {
const action = addDataToMap({
datasets: [{info: {id: 'test'}, data: []}]
});
expect(action.type).toBe('ADD_DATA_TO_MAP');
expect(action.payload.datasets[0].info.id).toBe('test');
});
Middleware тестируется через mock store:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const mockStore = configureStore([thunk]);
test('dispatches ADD_DATASET after async action', async () => {
const store = mockStore({});
await store.dispatch(async (dispatch) => {
dispatch({type: 'ADD_DATASET'});
});
const actions = store.getActions();
expect(actions[0].type).toBe('ADD_DATASET');
});
UI-часть Kepler.gl включает компоненты управления слоями, фильтрами, панелью настроек и самой картой.
Основная сложность тестирования компонентов — зависимость от контекста Redux и внешних провайдеров.
Пример тестирования компонента с React Testing Library:
import {render} from '@testing-library/react';
import LayerPanel from 'kepler.gl/components';
test('LayerPanel renders without crashing', () => {
const props = {
layers: [],
datasets: {}
};
const {container} = render(<LayerPanel {...props} />);
expect(container).toBeDefined();
});
Для компонентов Kepler.gl часто требуется оборачивание в Provider:
import {Provider} from 'react-redux';
import configureStore from 'redux-mock-store';
const store = configureStore([])({keplerGl: {}});
render(
<Provider store={store}>
<LayerPanel />
</Provider>
);
Kepler.gl использует Mapbox GL и deck.gl, которые невозможно запускать в стандартной тестовой среде Jest без эмуляции.
Основные подходы:
jest.mock('react-map-gl', () => ({
StaticMap: () => null,
MapGL: () => null
}));
global.URL.createObjectURL = jest.fn();
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({})
})
);
HTMLCanvasElement.prototype.getContext = () => ({
clearColor: jest.fn(),
enable: jest.fn(),
viewport: jest.fn()
});
Такая изоляция позволяет запускать тесты в CI без GPU окружения.
Kepler.gl имеет сложную структуру state tree:
Проверка корректной инициализации состояния часто важнее, чем тестирование отдельных UI действий.
import keplerGlReducer from 'kepler.gl/reducers';
test('initial state structure is correct', () => {
const state = keplerGlReducer(undefined, {type: '@@INIT'});
expect(state).toHaveProperty('visState');
expect(state).toHaveProperty('mapState');
expect(state).toHaveProperty('uiState');
});
В библиотеке присутствуют утилиты для обработки данных:
Пример теста утилиты обработки координат:
import {processGeojson} from 'kepler.gl/utils';
test('processGeojson should normalize features', () => {
const geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {type: 'Point', coordinates: [10, 20]}
}
]
};
const result = processGeojson(geojson);
expect(result.features[0].geometry.coordinates).toEqual([10, 20]);
});
Структура тестов обычно зеркалирует структуру исходного кода:
/reducers/__tests__
/selectors/__tests__
/actions/__tests__
/components/__tests__
/utils/__tests__
Такой подход упрощает навигацию и поддержку тестового покрытия.
Дополнительно часто выделяется отдельная папка для:
Snapshot-тесты применяются для UI-компонентов, где важно сохранить стабильность визуальной структуры.
import renderer from 'react-test-renderer';
test('LayerManager snapshot', () => {
const tree = renderer
.create(<LayerManager layers={[]} />)
.toJSON();
expect(tree).toMatchSnapshot();
});
Однако в Kepler.gl snapshot-тесты требуют осторожности из-за частых изменений UI и зависимостей от состояния Redux.
Kepler.gl активно использует иммутабельные структуры, поэтому важно избегать мутаций исходного state в тестах.
Практика:
const createMockState = () => ({
keplerGl: {
map: {
datasets: {}
}
}
});
Корректная изоляция зависимостей является ключевым фактором стабильности тестов и их воспроизводимости.