Testing асинхронных операций

Асинхронные операции в Backbone.js чаще всего связаны с взаимодействием с сервером через Backbone.Model и Backbone.Collection, использующих методы fetch, save и destroy. Тестирование таких операций требует внимания к синхронизации и корректной обработке обратных вызовов (callbacks) или промисов.


Основные подходы

1. Использование спаев и моков

Backbone.js предоставляет гибкость через возможность переопределения метода sync, который отвечает за коммуникацию с сервером. Для тестирования его обычно подменяют на мок-функцию.

describe('Model fetch', function() {
    var model;

    beforeEach(function() {
        model = new Backbone.Model();
        sinon.stub(model, 'sync').callsFake(function(method, model, options) {
            options.success({id: 1, name: 'Test'});
        });
    });

    it('должен обновлять атрибуты модели после fetch', function(done) {
        model.fetch({
            success: function() {
                expect(model.get('id')).to.equal(1);
                expect(model.get('name')).to.equal('Test');
                done();
            }
        });
    });
});

Ключевой момент: использование done в асинхронных тестах позволяет тестовому раннеру дождаться завершения операции перед проверкой утверждений.


2. Работа с промисами и fetch

Современные реализации Backbone позволяют использовать промисы для обработки результатов асинхронных операций. В тестах можно использовать async/await для упрощения кода.

it('fetch возвращает корректные данные', async function() {
    var model = new Backbone.Model();
    sinon.stub(model, 'sync').resolves({id: 42, title: 'Async Test'});

    await model.fetch();
    expect(model.get('id')).to.equal(42);
    expect(model.get('title')).to.equal('Async Test');
});

Использование resolves из sinon позволяет имитировать успешный ответ сервера без фактического сетевого запроса.


Проверка ошибок

Асинхронные операции могут завершаться с ошибкой. В Backbone.js для этого используется обратный вызов error. В тестах необходимо проверять корректную обработку этих ситуаций.

it('должен вызывать error при неудачном fetch', function(done) {
    var model = new Backbone.Model();
    sinon.stub(model, 'sync').callsFake(function(method, model, options) {
        options.error(model, 'Server error');
    });

    model.fetch({
        error: function(model, response) {
            expect(response).to.equal('Server error');
            done();
        }
    });
});

Особенность: важно убедиться, что тест не завершится раньше вызова error. Для этого используется done или промисы.


Тестирование коллекций

Коллекции наследуют поведение моделей, поэтому методы fetch, create и destroy также могут быть протестированы через моки sync.

describe('Collection fetch', function() {
    var collection;

    beforeEach(function() {
        collection = new Backbone.Collection();
        sinon.stub(collection, 'sync').callsFake(function(method, model, options) {
            options.success([{id: 1}, {id: 2}]);
        });
    });

    it('должна заполняться моделями после fetch', function(done) {
        collection.fetch({
            success: function() {
                expect(collection.length).to.equal(2);
                expect(collection.at(0).get('id')).to.equal(1);
                done();
            }
        });
    });
});

Ключевое: проверка корректного заполнения коллекции после асинхронного запроса.


Интеграционное тестирование с реальным сервером

Для проверки работы с настоящим API удобно использовать тестовый сервер, например через json-server или express. В этом случае тесты могут быть асинхронными, используя async/await.

it('fetch с реальным сервером', async function() {
    var model = new Backbone.Model({id: 1});
    await model.fetch({url: '/api/items/1'});
    expect(model.get('id')).to.equal(1);
    expect(model.get('name')).to.be.a('string');
});

Рекомендация: для интеграционных тестов желательно изолировать тестовую базу данных или использовать мок-сервер, чтобы избежать зависимости от внешнего состояния.


Лучшие практики

  • Всегда использовать done или промисы для корректного завершения асинхронных тестов.
  • Мокировать серверные запросы через sinon.stub(model, 'sync') или специализированные библиотеки (fetch-mock, nock).
  • Разделять юнит-тесты (модели и коллекции с моками) и интеграционные тесты (с реальным API).
  • Проверять не только успешные сценарии, но и ошибки, таймауты и невалидные ответы.
  • При большом количестве асинхронных тестов избегать глобальных зависимостей между тестами, чтобы каждый был изолирован.

Асинхронное тестирование Backbone.js требует тщательного контроля за временем выполнения операций и предсказуемости данных. Использование моков и промисов обеспечивает стабильность тестовой среды и минимизирует зависимость от внешних сервисов.