Сбой модульного теста на сервисе 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();
});
Обновить:
Вот спецификация для 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);
});
});