jasmine.clock().tick() does not work with $timeout and debounce, but works fine with setTimeout
-
02-01-2020 - |
質問
Below I have 3 functions that do exactly the same thing. Each one uses a different way of calling setTimeout, delay1() uses setTimeout directly, delay2() uses angularjs $timeout and delay3() uses lodash debounce. They all work fine.
The problems occurs when I test using Jasmine. setTimeout works fine with the jasmine.clock().tick() method, but $timeout and debounce don't
I am interested in getting debounce working with Jasmine. I know I can use $timeout.flush() with angularjs but $timeout and setTimeout give me problems elsewhere in my code where I am using it with leaflet maps. debounce works nicely with leaflet.
I have created a plunker here: plnkr where you will see the $timeout and debounce tests not passing while the setTimeout test passes.
Is there a way I can work around this problem? Thanks
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $timeout) {
$scope.name = 'World';
$scope.delayed1 = function(){
setTimeout(function(){
$scope.name = "Hello world by setTimeout";
},500)
}
$scope.delayed2 = function(){
$timeout(function(){
$scope.name = "Hello world by $timeout";
},500)
}
$scope.delayed3 = function(){
_.debounce(function(){
$scope.name = "Hello world by debounce";
},500)
}
});
spec
describe('Testing a Hello World controller', function() {
var $scope = null;
var ctrl = null;
//you need to indicate your module in a test
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope
});
}));
it('should say hallo to the World', function() {
expect($scope.name).toEqual('World');
});
it('should say Hello world by setTimeout', function() {
jasmine.clock().install();
$scope.delayed1();
jasmine.clock().tick(600);
expect($scope.name).toEqual('Hello world by setTimeout');
jasmine.clock().uninstall();
});
it('should say Hello world by timeout', function() {
jasmine.clock().install();
$scope.delayed2();
jasmine.clock().tick(600);
expect($scope.name).toEqual('Hello world by timeout');
jasmine.clock().uninstall();
});
it('should say Hello world by debouce', function() {
jasmine.clock().install();
$scope.delayed3();
jasmine.clock().tick(600);
expect($scope.name).toEqual('Hello world by debouce');
jasmine.clock().uninstall();
});
});
解決
The clock in Jasmine will only work if you're testing setInterval()
or setTimeout()
functions directly as it just mocks those functions specifically to run synchronously. I believe there's a pull request for Jasmine to mock the Date object, which would allow for testing functions like _.debounce()
without mocking it, but I don't remember if that was merged in or not.
To test _.debounce()
you'll have to mock it to run synchronously, preferably as a spy or you can just override the function. Here's what I've found to work for me:
spyOn(_, 'debounce').and.callFake(function (func) {
return function () {
func.apply(this, arguments);
};
});
Now calls to _.debounce()
will run synchronously and tests should complete successfully. Of course you'll still have to use $timeout.flush()
.
I updated your plunker with these changes: http://plnkr.co/edit/KXmwcf1faUNf8nlqPeyd
他のヒント
In addition to @JDWardle's answer you maye also want to create a spy for the debounce cancel method.
spyOn(_, 'debounce').and.callFake(function (func) {
var mockDebounce = function () {
func.apply(this, arguments);
};
mockDebounce.cancel = jasmine.createSpy('cancel');
return mockDebounce;
});
The debounce function from lodash
uses the Date
object. You mock the Date
object using jasmine
like this:
jasmine.clock().install();
jasmine.clock().mockDate();
jasmine.clock().tick(1000); // trigger the debounce
Source: https://jasmine.github.io/2.9/introduction.html#section-Mocking_the_Date