Сбой модульного теста на сервисе Angular с обещаниями

JSFiddle показывает проблему:

http://jsfiddle.net/ggvfwoeL/3/

У меня есть приложение Angular, которое включает сервис для генерации угроз. Создание угрозы осуществляется с помощью механизма правил nools. Сервис выставляет функцию generateForElement это возвращает обещание. Функция выглядит следующим образом (подробности см. В JSFiddle):

var Element = function (element) { this.type = element.attributes.type; }
var Threats = function () { this.collection = []; }    

    function generateForElement(element) {
           var threats = new Threats();
           var el = new Element(element);
           flow.getSession(threats, el).match();
           return $q.when(threats.collection);
    }

Механизм правил настроен так, чтобы при вызове generateForElement с элементом, который является tm.Process типа, он генерирует 2 угрозы. Вот код, который определяет правила (очевидно, это значительно упрощает, чтобы сделать вопрос понятнее):

function initialiseFlow() {
        return nools.flow('Element threat generation', function (flow) {

            flow.rule('Spoofing Threat Rule', [
                [Element, 'el', 'el.type == "tm.Process"'],
                [Threats, 'threats']
            ], function (facts) {
                facts.threats.collection.push('Spoofing');
            });

            flow.rule('Tampering Threat Rule', [
                [Element, 'el', 'el.type == "tm.Process"'],
                [Threats, 'threats']
            ], function (facts) {
                facts.threats.collection.push('Tampering');
            });
        });
    }

Когда я вручную проверяю это в своем приложении, я вижу, как генерируются 2 угрозы. Но мой модульный тест не проходит на этой линии

expect(threats.length).toEqual(2);

С ошибкой

Error: Expected 1 to equal 2.

Таким образом, создается впечатление, что генерируется только 1 угроза. Определение модульного теста выглядит так:

describe('threatengine service', function () {

    var threatengine;
    var $rootScope;

    beforeEach(function () {

        angular.mock.module('app')

        angular.mock.inject(function (_$rootScope_, _threatengine_) {
            threatengine = _threatengine_;
            $rootScope = _$rootScope_;
        });

        $rootScope.$apply();
    });

    describe('threat generation tests', function () {

        it('process should generate two threats', function () {

            var element = { attributes: { type: 'tm.Process' }};
            var threats;
            threatengine.generateForElement(element).then(function (data) {
                threats = data;
            });
            expect(threats).toBeUndefined();
            $rootScope.$apply();
            expect(threats).toBeDefined();
            expect(threats.length).toEqual(2);
        });
    });
});

Очевидно, я делаю что-то не так. Как я уже сказал, когда я запускаю полное приложение, я определенно получаю 2 угрозы, которые заставляют меня думать, что проблема заключается либо в модульном тесте, либо, возможно, в том, как я выполняю обещания в своем сервисе, но я просто не вижу этого.

Почему мой модульный тест не проходит?

1 ответ

Решение

Первая проблема в flow.getSession(threats, el).match(); вызов, который вызывается синхронно в вашем коде, но изначально он кажется асинхронным (я не знаком с nools, но вот документы). Так что даже если вы разместите console.log Внутри обработчиков обоих правил вы увидите, что правила обрабатываются намного позже, чем ваш следующий код синхронизации. Решение для этого состоит в том, чтобы использовать обещание, которое .match() возвращает:

function generateForElement(element) {
    var threats = new Threats();
    var el = new Element(element);
    // handle async code via promises and resolve it with custom collection
    return flow.getSession(threats, el).match().then(function () {
      return threats.collection;
    });
}

Другая проблема находится в тестовом файле. Там у вас также есть асинхронный код, но вы обрабатываете его как код синхронизации. Посмотрите Асинхронную Поддержку в документах Жасмина. По сути, вы должны сообщить Jasmine, если ваши тесты асинхронные, и уведомить об этом, когда они будут выполнены.

it('process should generate two threats', function (done) {
    // telling Jasmine that code is async ----------^^^

    var element = { attributes: { type: 'tm.Process' }};

    // this call is async
    threatengine.generateForElement(element).then(function (threats) {
        expect(threats).toBeUndefined();
        expect(threats).toBeDefined();
        expect(threats.length).toEqual(2);

        done(); // telling Jasmine that code has completed
    });

    // is required to start promises cycle if any
    $rootScope.$apply();
});

Вот рабочий JSFiddle.

Обновить:

Вот спецификация для Jasmine 1.3, он использует другой API для асинхронного потока:

it('process should generate two threats', function (done) {

    var element = { attributes: { type: 'tm.Process' }};
    var threats;

    runs(function () {
        threatengine.generateForElement(element).then(function (data) {
            threats = data;
        });
        $rootScope.$apply();
        });

        waitsFor(function () {
            return typeof threats !== 'undefined';
    });

    runs(function () {
        expect(threats).not.toBeUndefined();
        expect(threats).toBeDefined();
        expect(threats.length).toEqual(20);
    });

});
Другие вопросы по тегам