Setting Up Jest Tests for Vue/React Components in 1C-Bitrix
Vue and React components in 1C-Bitrix projects are the easiest part to test in isolation. A component accepts props and returns DOM — it is a pure function. Jest with Testing Library makes it possible to test components without a browser, quickly, in a Node.js environment. For a Bitrix project this means: frontend tests run in seconds, require no running server, and integrate into CI without Docker.
Setting Up Jest Tests for Vue/React Components in 1C-Bitrix
Test Structure in a Bitrix Project
/local/templates/my_site/
src/
components/
catalog/
ProductCard.vue <- component
ProductCard.test.ts <- test next to component
cart/
CartItem.tsx
CartItem.test.tsx
composables/
useCart.ts
useCart.test.ts
jest.config.ts
package.json
Keeping tests next to components is more convenient than a separate __tests__/ folder: when a component is moved during refactoring, its tests move with it.
jest.config.ts for 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',
// Mock static assets
'\\.(css|scss|png|jpg|svg)$': '<rootDir>/src/__mocks__/fileMock.ts',
// Global BX object — mocked
'^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;
Mock for the BX and BX24 Objects
// src/__mocks__/bx.ts
// Bitrix global object — mocked for isolated tests
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(),
};
Test for the Vue Product Card Component
// src/components/catalog/ProductCard.test.ts
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest'; // or jest.fn()
import ProductCard from './ProductCard.vue';
import * as cartApi from '@/api/cart';
const mockProduct = {
id: '42',
name: 'Bosch GSB 21-2 RCT Drill',
price: '8990',
currency: 'RUB',
img: '/upload/test.jpg',
inStock: true,
};
describe('ProductCard', () => {
it('renders the product name and price', () => {
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('shows the "Add to Cart" button for an in-stock product', () => {
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('hides the "Add to Cart" button for an out-of-stock product', () => {
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('calls the cart API on "Add to Cart" click', 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,
});
});
});
Test for a React Component (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 Rotary Hammer',
price: 12490,
quantity: 2,
img: null,
};
describe('CartItem', () => {
it('renders the name and total price', () => {
render(<CartItem item={mockItem} onRemove={jest.fn()} onQuantityChange={jest.fn()} />);
expect(screen.getByText('Makita HR2630 Rotary Hammer')).toBeInTheDocument();
expect(screen.getByText('24 980 ₽')).toBeInTheDocument(); // 12490 * 2
});
it('calls onQuantityChange when quantity is changed', 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);
});
});
Test for a 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('initializes with an empty cart', () => {
const { items, totalCount, totalPrice } = useCart();
expect(items.value).toEqual([]);
expect(totalCount.value).toBe(0);
expect(totalPrice.value).toBe(0);
});
it('adds a product and updates state', 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);
});
});
Running Tests
# All tests
npx jest
# With coverage
npx jest --coverage
# Watch mode during development
npx jest --watch
# Specific file
npx jest ProductCard
Timelines
| Task | Timeline |
|---|---|
| Set up Jest + @vue/test-utils or @testing-library/react | 4–8 hours |
| Basic tests for 5–10 components | 1–2 days |
| Tests for composables / hooks with Pinia/Zustand | 1 day |
| CI integration, coverage report configuration | 4 hours |







