问题
I have a decorator in Angular that is going to extend the functionality of the $log service and I would like to test it, but I don't see a way to do this. Here is a stub of my decorator:
angular.module('myApp')
.config(function ($provide) {
$provide.decorator('$log', ['$delegate', function($delegate) {
var _debug = $delegate.debug;
$delegate.debug = function() {
var args = [].slice.call(arguments);
// Do some custom stuff
window.console.info('inside delegated method!');
_debug.apply(null, args);
};
return $delegate
}]);
});
Notice that this basically overrides the $log.debug()
method, then calls it after doing some custom stuff. In my app this works and I see the 'inside delegated method!'
message in the console. But in my test I do not get that output.
How can I test my decorator functionality??
Specifically, how can I inject my decorator such that it actually decorates my $log
mock implementation (see below)?
Here is my current test (mocha/chai, but that isn't really relevant):
describe('Log Decorator', function () {
var MockNativeLog;
beforeEach(function() {
MockNativeLog = {
debug: chai.spy(function() { window.console.log("\nmock debug call\n"); })
};
});
beforeEach(angular.mock.module('myApp'));
beforeEach(function() {
angular.mock.module(function ($provide) {
$provide.value('$log', MockNativeLog);
});
});
describe('The logger', function() {
it('should go through the delegate', inject(function($log) {
// this calls my mock (above), but NOT the $log decorator
// how do I get the decorator to delegate the $log module??
$log.debug();
MockNativeLog.debug.should.have.been.called(1);
}));
});
});
回答1:
From the attached plunk (http://j.mp/1p8AcLT), the initial version is the (mostly) untouched code provided by @jakerella (minor adjustments for syntax). I tried to use the same dependencies I could derive from the original post. Note tests.js:12-14
:
angular.mock.module(function ($provide) {
$provide.value('$log', MockNativeLog);
});
This completely overrides the native $log
Service, as you might expect, with the MockNativeLog
implementation provided at the beginning of the tests because angular.mock.module(fn) acts as a config function for the mock module. Since the config functions execute in FIFO order, this function clobbers the decorated $log
Service.
One solution is to re-apply the decorator inside that config function, as you can see from version 2 of the plunk (permalink would be nice, Plunker), tests.js:12-18
:
angular.mock.module('myApp', function ($injector, $provide) {
// This replaces the native $log service with MockNativeLog...
$provide.value('$log', MockNativeLog);
// This decorates MockNativeLog, which _replaces_ MockNativeLog.debug...
$provide.decorator('$log', logDecorator);
});
That's not enough, however. The decorator @jakerella defines replaces the debug
method of the $log
service, causing the later call to MockNativeLog.debug.should.be.called(1)
to fail. The method MockNativeLog.debug
is no longer a spy provided by chai.spy
, so the matchers won't work.
Instead, note that I created an additional spy in tests.js:2-8
:
var MockNativeLog, MockDebug;
beforeEach(function () {
MockNativeLog = {
debug: MockDebug = chai.spy(function () {
window.console.log("\nmock debug call\n");
})
};
});
That code could be easier to read:
MockDebug = chai.spy(function () {
window.console.log("\nmock debug call\n");
});
MockNativeLog = {
debug: MockDebug
};
And this still doesn't represent a good testing outcome, just a sanity check. That's a relief after banging your head against the "why don't this work" question for a few hours.
Note that I additionally refactored the decorator function into the global scope so that I could use it in tests.js
without having to redefine it. Better would be to refactor into a proper Service with $provider.value()
, but that task has been left as an exercise for the student... Or someone less lazy than myself. :D
来源:https://stackoverflow.com/questions/22488056/how-to-test-angular-decorator-functionality