Writing integration tests for 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

Writing Integration Tests for 1C-Bitrix

You deploy an update, and in an hour discover that 1C import stopped creating new products — method signature changed in catalog module, but integration code wasn't updated. Integration tests solve exactly this problem: they verify your code correctly interacts with Bitrix core, database and external systems. Not unit-tests in vacuum, but real scenarios with real core.

How Integration Tests Differ from Unit Tests in Bitrix Context

Unit-testing Bitrix project is useless without mocking half of core. Class calling CIBlockElement::GetList() depends on iblocks, DB, cache, access rights. Mocking all this — writing second Bitrix. Integration tests load real kernel, work with real (test) DB and check full cycle.

Typical scenarios:

  • Creating order via CSaleOrder::Add() / \Bitrix\Sale\Order::create() — verify all event handlers trigger, discounts apply, status correct
  • XML product import — verify field mapping, section creation, price update
  • Generating 1C export — verify XML structure, data correctness
  • Processing payment system webhook — verify order status change

Test Environment Setup

PHPUnit + Bitrix kernel. Bitrix doesn't ship with test infrastructure, setup manually.

File bootstrap.php for PHPUnit:

$_SERVER['DOCUMENT_ROOT'] = '/path/to/site';
$_SERVER['HTTP_HOST'] = 'test.local';
$_SERVER['SERVER_NAME'] = 'test.local';
$GLOBALS['DBType'] = 'mysql'; // or pgsql

define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
define('BX_NO_ACCELERATOR_RESET', true);
define('STOP_STATISTICS', true);

require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

Constants NO_KEEP_STATISTIC and STOP_STATISTICS disable statistics logging, NOT_CHECK_PERMISSIONS — permission check (otherwise tests depend on current user).

Test DB. Two approaches:

  1. Separate DB — copy of production schema without data. Safe, but requires schema sync maintenance.
  2. Transactions — each test wrapped in transaction, rolled back in tearDown(). Fast, but doesn't work for tests using transactions themselves (nested transactions in MySQL behave unexpectedly).

Recommended approach for Bitrix — transactions with fallback to manual cleanup:

protected function setUp(): void
{
    \Bitrix\Main\Application::getConnection()->startTransaction();
}

protected function tearDown(): void
{
    \Bitrix\Main\Application::getConnection()->rollbackTransaction();
}

Test Structure

Place tests in /local/tests/ with mirrored structure:

/local/tests/
  Integration/
    Catalog/
      ImportTest.php      → product import tests
      PriceCalculationTest.php
    Sale/
      OrderCreationTest.php
      DiscountTest.php
    Exchange/
      OneCExportTest.php
  bootstrap.php
  phpunit.xml

phpunit.xml:

<phpunit bootstrap="bootstrap.php">
    <testsuites>
        <testsuite name="Integration">
            <directory>Integration</directory>
        </testsuite>
    </testsuites>
</phpunit>

Writing Tests: Bitrix Patterns

Order creation test:

public function testOrderCreationWithDiscount(): void
{
    // Arrange: create test product and discount
    $productId = $this->createTestProduct('TEST-001', 1000);
    $discountId = $this->createTestDiscount(10); // 10%

    // Act: create order
    $order = \Bitrix\Sale\Order::create('s1', 1);
    $basket = \Bitrix\Sale\Basket::create('s1');
    $item = $basket->createItem('catalog', $productId);
    $item->setFields(['QUANTITY' => 1, 'CURRENCY' => 'RUB', 'PRODUCT_PROVIDER_CLASS' => '\CCatalogProductProvider']);
    $order->setBasket($basket);
    $order->doFinalAction(true);
    $result = $order->save();

    // Assert
    $this->assertTrue($result->isSuccess());
    $this->assertEquals(900, $order->getPrice()); // 1000 - 10%
}

XML import test:

Don't call CIBlockCMLImport directly — it's heavy and poorly controlled. Test your wrapper code calling Bitrix API:

public function testProductImportCreatesElement(): void
{
    $importer = new \Project\Import\ProductImporter(CATALOG_IBLOCK_ID);
    $result = $importer->import([
        'XML_ID' => 'TEST-IMPORT-001',
        'NAME' => 'Test product',
        'PRICE' => 500,
    ]);

    $this->assertTrue($result->isSuccess());

    $element = \CIBlockElement::GetList(
        [],
        ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'XML_ID' => 'TEST-IMPORT-001'],
        false, false, ['ID', 'NAME']
    )->Fetch();

    $this->assertNotFalse($element);
    $this->assertEquals('Test product', $element['NAME']);
}

Event Handler Handling in Tests

Bitrix event handlers (OnBeforeIBlockElementAdd, OnSaleOrderBefore, etc.) — source of unexpected behavior. Handler registered in init.php fires in test environment too.

Two approaches:

  1. Accept as given — test system completely, including handlers. More correct for integration testing.
  2. Temporarily disableRemoveEventHandler() in setUp(), restore in tearDown(). Use when handler calls external service (email sending, API request).

What NOT to Test Integrally

  • Rendering and visual display — E2E-test job (Playwright, Selenium)
  • Pure business logic without Bitrix dependencies — unit-test job
  • Performance — integration tests slow by definition, use separate benchmark framework

Timeline for Test Writing

Coverage Volume Timeline
Critical path (checkout, import) 15-25 tests 3-5 days
Core functionality 50-80 tests 1-2 weeks
Extended coverage (edge cases, errors) 100+ tests 3-4 weeks

Start with tests for places that break most often. Usually this is 1C import/export and price calculation with discounts.