При тестировании компонентов, зависящих от внешних модулей и побочных эффектов, ключевой задачей становится контроль окружения. Библиотека выбора значений в интерфейсе активно взаимодействует с DOM, событиями, таймерами, сетевыми запросами и состоянием страницы. В таких условиях прямое тестирование приводит к нестабильным результатам: тесты становятся чувствительными к реализации зависимостей, времени выполнения и асинхронным операциям.
Для устранения этих проблем используются моки и стабы — техники подмены зависимостей управляемыми заглушками, позволяющими полностью контролировать поведение внешних компонентов.
В экосистеме тестирования часто смешиваются три близких концепции, хотя их назначение различается.
Stub (стаб) Используется для замены зависимости с заранее заданным поведением. Основная цель — вернуть фиксированные данные без выполнения реальной логики.
const apiStub = {
search: async (query) => {
return [
{ id: 1, text: "Alpha" },
{ id: 2, text: "Beta" }
];
}
};
Стаб не содержит логики проверки вызовов, он лишь подменяет результат.
Mock (мок) Расширяет стаб, добавляя возможность проверять взаимодействие: сколько раз вызван метод, с какими аргументами, в каком порядке.
const searchMock = jest.fn().mockResolvedValue([
{ id: 1, text: "Alpha" }
]);
Spy (шпион) Оборачивает реальную функцию и фиксирует её вызовы без полной подмены поведения.
const spy = jest.spyOn(console, "log");
Компонент управления выпадающими списками в JavaScript активно зависит от:
При тестировании напрямую возникают проблемы:
Поэтому ключевая стратегия — подмена внешних зависимостей через моки и стабы.
Во многих тестах требуется изолировать работу с DOM. Например, создание контейнера:
beforeEach(() => {
document.body.innerHTML = `<select id="select"></select>`;
});
Однако более сложные случаи требуют мокирования методов DOM API.
const querySelectorMock = jest.fn((selector) => {
if (selector === "#select") {
return document.createElement("select");
}
return null;
});
document.querySelector = querySelectorMock;
Такой подход позволяет контролировать структуру страницы независимо от реального DOM.
Tom Select часто используется с удалёнными данными через
load или кастомные функции поиска. В тестах важно исключить
реальные запросы.
const loadStub = (query, callback) => {
const data = [
{ value: "1", text: "Москва" },
{ value: "2", text: "Минск" }
];
callback(data);
};
Этот стаб заменяет реальный AJAX или fetch и обеспечивает детерминированный результат.
global.fetch = jest.fn(() =>
Promise.resolve({
json: () =>
Promise.resolve([
{ id: 1, name: "Item A" },
{ id: 2, name: "Item B" }
])
})
);
Такой мок позволяет полностью контролировать асинхронный поток данных.
При интеграционном тестировании важно изолировать поведение отдельных методов библиотеки.
const instance = new TomSelect("#select");
instance.addItem = jest.fn();
instance.addItem("value1");
expect(instance.addItem).toHaveBeenCalledWith("value1");
Здесь проверяется не результат работы метода, а факт вызова с корректными аргументами.
Tom Select активно генерирует события: change,
dropdown_open, item_add. Их удобно имитировать
через мок-функции.
const onChangeM ock = jest.fn();
const select = new TomSelect("#select", {
onChange: onChangeMock
});
select.setValue("123");
expect(onChangeMock).toHaveBeenCalled();
Такой подход позволяет тестировать реакцию системы без реального взаимодействия с UI.
Некоторые сценарии зависят от задержек, например debounce при поиске.
jest.useFakeTimers();
const select = new TomSelect("#select", {
loadThrottle: 300
});
select.load("test");
jest.advanceTimersByTime(300);
Фальшивые таймеры устраняют необходимость реального ожидания и делают тесты детерминированными.
При тестировании пользовательских действий часто требуется вручную генерировать события.
const selectEl = document.getElementById("select");
const event = new Event("change");
selectEl.dispatchEvent(event);
Для более сложных сценариев создаются стабы обработчиков событий:
const eventHandlerStub = jest.fn();
selectEl.addEventListener("change", eventHandlerStub);
selectEl.dispatchEvent(new Event("change"));
expect(eventHandlerStub).toHaveBeenCalled();
Конфигурационные параметры часто влияют на поведение компонентов. Для тестирования используется фиктивная конфигурация.
const configStub = {
maxItems: 3,
create: false,
placeholder: "Выбор"
};
const select = new TomSelect("#select", configStub);
При необходимости параметры также подменяются динамически:
select.settings.maxItems = 1;
Tom Select поддерживает плагины, которые могут изменять поведение.
Для тестирования используется отключение или подмена:
const select = new TomSelect("#select", {
plugins: []
});
Или мокирование плагина:
jest.mock("tom-select/plugins/drag_drop", () => {
return function () {
return {
init: jest.fn(),
destroy: jest.fn()
};
};
});
Основная ценность моков проявляется в проверке взаимодействий, а не результата.
const removeItemMock = jest.fn();
const select = new TomSelect("#select");
select.removeItem = removeItemMock;
select.removeItem("value1");
expect(removeItemMock).toHaveBeenCalledTimes(1);
expect(removeItemMock).toHaveBeenCalledWith("value1");
Такой подход позволяет тестировать контракт поведения, а не внутреннюю реализацию.
На практике часто используется гибридный подход: стаб возвращает данные, мок фиксирует вызовы.
const loadMock = jest.fn((query, cb) => {
cb([{ value: "1", text: "A" }]);
});
const select = new TomSelect("#select", {
load: loadMock
});
select.load("test");
expect(loadMock).toHaveBeenCalledWith("test", expect.any(Function));
Tom Select хранит внутреннее состояние выбранных значений. В тестах его можно фиксировать вручную.
const select = new TomSelect("#select");
select.setValue("1");
select.setValue = jest.fn();
select.setValue("2");
expect(select.setValue).toHaveBeenCalled();
Чрезмерное использование моков приводит к тестам, которые проверяют не поведение системы, а сам факт вызовов. Это снижает ценность тестирования.
Проблемные паттерны:
Более устойчивый подход — мокировать только внешние зависимости, оставляя внутреннюю логику компонента активной.
Особое внимание требуется побочным эффектам: изменению DOM, добавлению классов, созданию элементов.
const select = new TomSelect("#select");
select.open();
expect(document.querySelector(".ts-dropdown")).not.toBeNull();
Если DOM нестабилен, используется частичная подмена:
document.createElement = jest.fn(() => {
return {
classList: {
add: jest.fn()
}
};
});
В тестировании компонентов уровня Tom Select формируется типовая схема:
Комбинация этих инструментов обеспечивает детерминированное поведение тестов и позволяет изолировать компонент от нестабильных внешних факторов.