Setting up Jest tests for Vue/React components in 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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