Налаштування Jest-тестів для Vue/React компонентів 1С-Бітрікс
Vue та React компоненти в проєктах на 1С-Бітрікс — частина, яку найлегше протестувати ізольовано. Компонент приймає пропси, повертає DOM — чиста функція. Jest з Testing Library дає можливість тестувати компоненти без браузера, швидко, в Node.js-середовищі. Для проєкту на Бітрікс це означає: тести фронтенду запускаються за секунди, не потребують запущеного сервера, інтегруються в CI без Docker.
Налаштування Jest-тестів для Vue/React компонентів 1С-Бітрікс
Структура тестів у проєкті на Бітрікс
/local/templates/my_site/
src/
components/
catalog/
ProductCard.vue <- компонент
ProductCard.test.ts <- тест поруч із компонентом
cart/
CartItem.tsx
CartItem.test.tsx
composables/
useCart.ts
useCart.test.ts
jest.config.ts
package.json
Тести поруч із компонентами — зручніше, ніж окрема папка __tests__/: при рефакторингу переміщення компонента і його тестів відбувається разом.
jest.config.ts для Vue + TypeScript
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.vue$': ['@vue/vue3-jest', { tsConfig: 'tsconfig.json' }],
'^.+\\.(ts|tsx|js|jsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
// Мокуємо статичні ресурси
'\\.(css|scss|png|jpg|svg)$': '<rootDir>/src/__mocks__/fileMock.ts',
// Глобальний об'єкт BX — мокуємо
'^bx-globals$': '<rootDir>/src/__mocks__/bx.ts',
},
moduleFileExtensions: ['ts', 'tsx', 'vue', 'js', 'json'],
coverageDirectory: 'coverage',
collectCoverageFrom: [
'src/components/**/*.{vue,ts,tsx}',
'src/composables/**/*.ts',
'!src/**/*.test.{ts,tsx}',
],
setupFilesAfterFramework: ['<rootDir>/src/test-setup.ts'],
};
export default config;
Мок об'єктів BX і BX24
// src/__mocks__/bx.ts
// Глобальний об'єкт Бітрікс — мокуємо для ізольованих тестів
global.BX = {
bitrix_sessid: () => 'test-sessid-12345',
message: (params: Record<string, string>) => params,
bind: jest.fn(),
Event: { add: jest.fn() },
};
global.BX24 = {
init: (cb: () => void) => cb(),
isAdmin: () => false,
callMethod: jest.fn(),
callBatch: jest.fn(),
resizeWindow: jest.fn(),
};
Тест Vue-компонента картки товару
// src/components/catalog/ProductCard.test.ts
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest'; // або jest.fn()
import ProductCard from './ProductCard.vue';
import * as cartApi from '@/api/cart';
const mockProduct = {
id: '42',
name: 'Дрель Bosch GSB 21-2 RCT',
price: '8990',
currency: 'RUB',
img: '/upload/test.jpg',
inStock: true,
};
describe('ProductCard', () => {
it('відображає назву та ціну товару', () => {
const wrapper = mount(ProductCard, {
props: { product: mockProduct },
});
expect(wrapper.find('.product-name').text()).toBe(mockProduct.name);
expect(wrapper.find('.product-price').text()).toContain('8 990');
});
it('показує кнопку «В кошик» для товару в наявності', () => {
const wrapper = mount(ProductCard, {
props: { product: mockProduct },
});
expect(wrapper.find('[data-action="add-to-cart"]').exists()).toBe(true);
expect(wrapper.find('.out-of-stock').exists()).toBe(false);
});
it('приховує кнопку «В кошик» для товару не в наявності', () => {
const wrapper = mount(ProductCard, {
props: { product: { ...mockProduct, inStock: false } },
});
expect(wrapper.find('[data-action="add-to-cart"]').exists()).toBe(false);
expect(wrapper.find('.out-of-stock').exists()).toBe(true);
});
it('викликає API кошика при кліку «В кошик»', async () => {
const addToCart = vi.spyOn(cartApi, 'addToCart').mockResolvedValue({
items: [],
totalPrice: 8990,
totalCount: 1,
currency: 'RUB',
});
const wrapper = mount(ProductCard, {
props: { product: mockProduct },
});
await wrapper.find('[data-action="add-to-cart"]').trigger('click');
await wrapper.vm.$nextTick();
expect(addToCart).toHaveBeenCalledWith({
productId: 42,
quantity: 1,
});
});
});
Тест React-компонента (TSX)
// src/components/cart/CartItem.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CartItem from './CartItem';
import * as cartApi from '@/api/cart';
const mockItem = {
id: 1,
name: 'Перфоратор Makita HR2630',
price: 12490,
quantity: 2,
img: null,
};
describe('CartItem', () => {
it('відображає назву та сумарну вартість', () => {
render(<CartItem item={mockItem} onRemove={jest.fn()} onQuantityChange={jest.fn()} />);
expect(screen.getByText('Перфоратор Makita HR2630')).toBeInTheDocument();
expect(screen.getByText('24 980 ₽')).toBeInTheDocument(); // 12490 * 2
});
it('викликає onQuantityChange при зміні кількості', async () => {
const onQuantityChange = jest.fn();
const user = userEvent.setup();
render(<CartItem item={mockItem} onRemove={jest.fn()} onQuantityChange={onQuantityChange} />);
const plusBtn = screen.getByRole('button', { name: '+' });
await user.click(plusBtn);
expect(onQuantityChange).toHaveBeenCalledWith(mockItem.id, 3);
});
});
Тест composable (Vue Composition API)
// src/composables/useCart.test.ts
import { setActivePinia, createPinia } from 'pinia';
import { useCart } from './useCart';
import * as cartApi from '@/api/cart';
vi.mock('@/api/cart');
describe('useCart', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it('ініціалізується з порожнім кошиком', () => {
const { items, totalCount, totalPrice } = useCart();
expect(items.value).toEqual([]);
expect(totalCount.value).toBe(0);
expect(totalPrice.value).toBe(0);
});
it('додає товар і оновлює стан', async () => {
vi.mocked(cartApi.addToCart).mockResolvedValue({
items: [{ id: 1, name: 'Test', price: 100, quantity: 1, img: null }],
totalPrice: 100,
totalCount: 1,
currency: 'RUB',
});
const { addItem, totalCount } = useCart();
await addItem(42, 1);
expect(totalCount.value).toBe(1);
});
});
Запуск тестів
# Усі тести
npx jest
# З покриттям
npx jest --coverage
# Watch-mode при розробці
npx jest --watch
# Конкретний файл
npx jest ProductCard
Терміни
| Завдання | Терміни |
|---|---|
| Налаштування Jest + @vue/test-utils або @testing-library/react | 4–8 годин |
| Базові тести для 5–10 компонентів | 1–2 дні |
| Тести composables / хуків з Pinia/Zustand | 1 день |
| Інтеграція в CI, конфігурація coverage-репорту | 4 години |







