JSFiddle showing the problem:
http://jsfiddle.net/ggvfwoeL/3/
I have an Angular application that involves a service for generating threats. The threat generation is done using the nools rule engine. The service exposes a function generateForElement
that returns a promise. The function looks like this (see the JSFiddle for full details):
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);
}
The rule engine is set up so that when you call generateForElement
with an element that is a tm.Process
type, it generates 2 threats. Here is the code that defines the rules (obviously this is much simplified to make the question clearer):
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');
});
});
}
When I manually test this in my application I see the 2 threats being generated. But my unit test is failing on this line
expect(threats.length).toEqual(2);
With error
Error: Expected 1 to equal 2.
So it appears that only 1 threat is being generated. The unit test definition is like this:
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);
});
});
});
Clearly I am doing something wrong. As I said, when I run the full application I definitely get 2 threats which make me think the fault is either with the unit test, or maybe with how I am handling the promises in my service, but I just can't see it.
Why is my unit test failing?
First problem is in
flow.getSession(threats, el).match();
call which is called synchronously in your code, but originally it seems to be asynchronous (I am not familiar with nools, but here are the docs). So even if you placeconsole.log
inside handlers for both rules, you'll see that rules get processed way more later than your following sync code. The solution for it is to use a promise, which.match()
returns:The other issue is in a test file. There, you also have async code, but you process it like sync code. See Asynchronous Support in Jasmine docs. Basically, you have to tell Jasmine if your tests are async and notify it when they are done.
Here is a working JSFiddle.
Update:
Here is a spec for Jasmine 1.3, it uses another API for async flow: