I'm trying to set a timeout in my controller so that if a response isn't received in 250ms it should fail. I've set my unit test to have a timeout of 10000 so that this condition should be met,Can anyone point me in the right direction? ( EDIT I'm trying to achieve this without using the $http service which I know provides timeout functinality)
我正在尝试在我的控制器中设置超时,以便如果在250ms内没有收到响应,则应该失败。我已将我的单元测试设置为超时10000,以便满足此条件,任何人都可以指出我正确的方向吗? (编辑我试图实现这一点,而不使用我知道提供超时功能的$ http服务)
(EDIT - my other unit tests were failing because I wasn't calling timeout.flush on them, now I just need to get the timeout message kicking in when an undefined promise is returned by promiseService.getPromise(). I've removed the early code from the question) .
(编辑 - 我的其他单元测试失败,因为我没有调用timeout.flush,现在我只需要在promiseService.getPromise()返回未定义的promise时获取超时消息。我已经删除了来自问题的早期代码)。
promiseService (promise is a test suite variable allowing me to use different behaviour for the promise in each test suite before apply, eg reject in one, success in another)
promiseService(promise是一个测试套件变量,允许我在应用之前对每个测试套件中的promise使用不同的行为,例如拒绝一个,成功另一个)
mockPromiseService = jasmine.createSpyObj('promiseService', ['getPromise']);
mockPromiseService.getPromise.andCallFake( function() {
promise = $q.defer();
return promise.promise;
})
Controller function that's being tested -
正在测试的控制器功能 -
$scope.qPromiseCall = function() {
var timeoutdata = null;
$timeout(function() {
promise = promiseService.getPromise();
promise.then(function (data) {
timeoutdata = data;
if (data == "promise success!") {
console.log("success");
} else {
console.log("function failure");
}
}, function (error) {
console.log("promise failure")
}
)
}, 250).then(function (data) {
if(typeof timeoutdata === "undefined" ) {
console.log("Timed out")
}
},function( error ){
console.log("timed out!");
});
}
Test (normally I resolve or reject the promise in here but by not setting it I'm simulating a timeout)
测试(通常我在这里解决或拒绝承诺但是没有设置它我正在模拟超时)
it('Timeout logs promise failure', function(){
spyOn(console, 'log');
scope.qPromiseCall();
$timeout.flush(251);
$rootScope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
})
3 个解决方案
#1
29
First, I would like to say that your controller implementation should be something like this:
首先,我想说你的控制器实现应该是这样的:
$scope.qPromiseCall = function() {
var timeoutPromise = $timeout(function() {
canceler.resolve(); //aborts the request when timed out
console.log("Timed out");
}, 250); //we set a timeout for 250ms and store the promise in order to be cancelled later if the data does not arrive within 250ms
var canceler = $q.defer();
$http.get("data.js", {timeout: canceler.promise} ).success(function(data){
console.log(data);
$timeout.cancel(timeoutPromise); //cancel the timer when we get a response within 250ms
});
}
Your tests:
你的测试:
it('Timeout occurs', function() {
spyOn(console, 'log');
$scope.qPromiseCall();
$timeout.flush(251); //timeout occurs after 251ms
//there is no http response to flush because we cancel the response in our code. Trying to call $httpBackend.flush(); will throw an exception and fail the test
$scope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
})
it('Timeout does not occur', function() {
spyOn(console, 'log');
$scope.qPromiseCall();
$timeout.flush(230); //set the timeout to occur after 230ms
$httpBackend.flush(); //the response arrives before the timeout
$scope.$apply();
expect(console.log).not.toHaveBeenCalledWith("Timed out");
})
DEMO
Another example with promiseService.getPromise
:
promiseService.getPromise的另一个例子:
app.factory("promiseService", function($q,$timeout,$http) {
return {
getPromise: function() {
var timeoutPromise = $timeout(function() {
console.log("Timed out");
defer.reject("Timed out"); //reject the service in case of timeout
}, 250);
var defer = $q.defer();//in a real implementation, we would call an async function and
// resolve the promise after the async function finishes
$timeout(function(data){//simulating an asynch function. In your app, it could be
// $http or something else (this external service should be injected
//so that we can mock it in unit testing)
$timeout.cancel(timeoutPromise); //cancel the timeout
defer.resolve(data);
});
return defer.promise;
}
};
});
app.controller('MainCtrl', function($scope, $timeout, promiseService) {
$scope.qPromiseCall = function() {
promiseService.getPromise().then(function(data) {
console.log(data);
});//you could pass a second callback to handle error cases including timeout
}
});
Your tests are similar to the above example:
您的测试与上面的示例类似:
it('Timeout occurs', function() {
spyOn(console, 'log');
spyOn($timeout, 'cancel');
$scope.qPromiseCall();
$timeout.flush(251); //set it to timeout
$scope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
//expect($timeout.cancel).not.toHaveBeenCalled();
//I also use $timeout to simulate in the code so I cannot check it here because the $timeout is flushed
//In real app, it is a different service
})
it('Timeout does not occur', function() {
spyOn(console, 'log');
spyOn($timeout, 'cancel');
$scope.qPromiseCall();
$timeout.flush(230);//not timeout
$scope.$apply();
expect(console.log).not.toHaveBeenCalledWith("Timed out");
expect($timeout.cancel).toHaveBeenCalled(); //also need to check whether cancel is called
})
DEMO
#2
8
The behaviour of "failing a promise unless it is resolved with a specified timeframe" seems ideal for refactoring into a separate service/factory. This should make the code in both the new service/factory and controller clearer and more re-usable.
“失败的承诺除非在指定的时间范围内解决”的行为似乎是重构为单独的服务/工厂的理想选择。这应该使新服务/工厂和控制器中的代码更清晰,更可重用。
The controller, which I've assumed just sets the success/failure on the scope:
控制器,我假设只是在范围上设置成功/失败:
app.controller('MainCtrl', function($scope, failUnlessResolvedWithin, myPromiseService) {
failUnlessResolvedWithin(function() {
return myPromiseService.getPromise();
}, 250).then(function(result) {
$scope.result = result;
}, function(error) {
$scope.error = error;
});
});
And the factory, failUnlessResolvedWithin
, creates a new promise, which effectively "intercepts" a promise from a passed in function. It returns a new one that replicates its resolve/reject behaviour, except that it also rejects the promise if it hasn't been resolved within the timeout:
而工厂,failUnlessResolvedWithin,创造了一个新的承诺,它有效地“拦截”来自传入函数的承诺。它返回一个复制其解析/拒绝行为的新的,除了它还拒绝了在超时内未解析的promise:
app.factory('failUnlessResolvedWithin', function($q, $timeout) {
return function(func, time) {
var deferred = $q.defer();
$timeout(function() {
deferred.reject('Not resolved within ' + time);
}, time);
$q.when(func()).then(function(results) {
deferred.resolve(results);
}, function(failure) {
deferred.reject(failure);
});
return deferred.promise;
};
});
The tests for these are a bit tricky (and long), but you can see them at http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview . The main points of the tests are
这些测试有点棘手(很长),但你可以在http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview看到它们。测试的要点是
-
The tests for the controller mocks
failUnlessResolvedWithin
with a call to$timeout
.控制器的测试模拟failUnlessResolvedWithin并调用$ timeout。
$provide.value('failUnlessResolvedWithin', function(func, time) { return $timeout(func, time); });
This is possible since 'failUnlessResolvedWithin' is (deliberately) syntactically equivalent to
$timeout
, and done since$timeout
provides theflush
function to test various cases.这是可能的,因为'failUnlessResolvedWithin'(故意)在语法上等同于$ timeout,并且因为$ timeout提供了flush函数来测试各种情况。
-
The tests for the service itself uses calls
$timeout.flush
to test behaviour of the various cases of the original promise being resolved/rejected before/after the timeout.服务本身的测试使用调用$ timeout.flush来测试在超时之前/之后解析/拒绝的原始承诺的各种情况的行为。
beforeEach(function() { failUnlessResolvedWithin(func, 2) .catch(function(error) { failResult = error; }); }); beforeEach(function() { $timeout.flush(3); $rootScope.$digest(); }); it('the failure callback should be called with the error from the service', function() { expect(failResult).toBe('Not resolved within 2'); });
You can see all this in action at http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview
你可以在http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview看到这一切
#3
1
My implementation of @Michal Charemza 's failUnlessResolvedWithin with a real sample. By passing deferred object to the func it reduces having to instantiate a promise in usage code "ByUserPosition". Helps me deal with firefox and geolocation.
我实现了@Michal Charemza的failUnlessResolvedWithin和一个真实的样本。通过将延迟对象传递给func,它减少了必须在使用代码“ByUserPosition”中实例化一个promise。帮助我处理firefox和地理定位。
.factory('failUnlessResolvedWithin', ['$q', '$timeout', function ($q, $timeout) {
return function(func, time) {
var deferred = $q.defer();
$timeout(function() {
deferred.reject('Not resolved within ' + time);
}, time);
func(deferred);
return deferred.promise;
}
}])
$scope.ByUserPosition = function () {
var resolveBy = 1000 * 30;
failUnlessResolvedWithin(function (deferred) {
navigator.geolocation.getCurrentPosition(
function (position) {
deferred.resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude });
},
function (err) {
deferred.reject(err);
}, {
enableHighAccuracy : true,
timeout: resolveBy,
maximumAge: 0
});
}, resolveBy).then(findByPosition, function (data) {
console.log('error', data);
});
};
#1
29
First, I would like to say that your controller implementation should be something like this:
首先,我想说你的控制器实现应该是这样的:
$scope.qPromiseCall = function() {
var timeoutPromise = $timeout(function() {
canceler.resolve(); //aborts the request when timed out
console.log("Timed out");
}, 250); //we set a timeout for 250ms and store the promise in order to be cancelled later if the data does not arrive within 250ms
var canceler = $q.defer();
$http.get("data.js", {timeout: canceler.promise} ).success(function(data){
console.log(data);
$timeout.cancel(timeoutPromise); //cancel the timer when we get a response within 250ms
});
}
Your tests:
你的测试:
it('Timeout occurs', function() {
spyOn(console, 'log');
$scope.qPromiseCall();
$timeout.flush(251); //timeout occurs after 251ms
//there is no http response to flush because we cancel the response in our code. Trying to call $httpBackend.flush(); will throw an exception and fail the test
$scope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
})
it('Timeout does not occur', function() {
spyOn(console, 'log');
$scope.qPromiseCall();
$timeout.flush(230); //set the timeout to occur after 230ms
$httpBackend.flush(); //the response arrives before the timeout
$scope.$apply();
expect(console.log).not.toHaveBeenCalledWith("Timed out");
})
DEMO
Another example with promiseService.getPromise
:
promiseService.getPromise的另一个例子:
app.factory("promiseService", function($q,$timeout,$http) {
return {
getPromise: function() {
var timeoutPromise = $timeout(function() {
console.log("Timed out");
defer.reject("Timed out"); //reject the service in case of timeout
}, 250);
var defer = $q.defer();//in a real implementation, we would call an async function and
// resolve the promise after the async function finishes
$timeout(function(data){//simulating an asynch function. In your app, it could be
// $http or something else (this external service should be injected
//so that we can mock it in unit testing)
$timeout.cancel(timeoutPromise); //cancel the timeout
defer.resolve(data);
});
return defer.promise;
}
};
});
app.controller('MainCtrl', function($scope, $timeout, promiseService) {
$scope.qPromiseCall = function() {
promiseService.getPromise().then(function(data) {
console.log(data);
});//you could pass a second callback to handle error cases including timeout
}
});
Your tests are similar to the above example:
您的测试与上面的示例类似:
it('Timeout occurs', function() {
spyOn(console, 'log');
spyOn($timeout, 'cancel');
$scope.qPromiseCall();
$timeout.flush(251); //set it to timeout
$scope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
//expect($timeout.cancel).not.toHaveBeenCalled();
//I also use $timeout to simulate in the code so I cannot check it here because the $timeout is flushed
//In real app, it is a different service
})
it('Timeout does not occur', function() {
spyOn(console, 'log');
spyOn($timeout, 'cancel');
$scope.qPromiseCall();
$timeout.flush(230);//not timeout
$scope.$apply();
expect(console.log).not.toHaveBeenCalledWith("Timed out");
expect($timeout.cancel).toHaveBeenCalled(); //also need to check whether cancel is called
})
DEMO
#2
8
The behaviour of "failing a promise unless it is resolved with a specified timeframe" seems ideal for refactoring into a separate service/factory. This should make the code in both the new service/factory and controller clearer and more re-usable.
“失败的承诺除非在指定的时间范围内解决”的行为似乎是重构为单独的服务/工厂的理想选择。这应该使新服务/工厂和控制器中的代码更清晰,更可重用。
The controller, which I've assumed just sets the success/failure on the scope:
控制器,我假设只是在范围上设置成功/失败:
app.controller('MainCtrl', function($scope, failUnlessResolvedWithin, myPromiseService) {
failUnlessResolvedWithin(function() {
return myPromiseService.getPromise();
}, 250).then(function(result) {
$scope.result = result;
}, function(error) {
$scope.error = error;
});
});
And the factory, failUnlessResolvedWithin
, creates a new promise, which effectively "intercepts" a promise from a passed in function. It returns a new one that replicates its resolve/reject behaviour, except that it also rejects the promise if it hasn't been resolved within the timeout:
而工厂,failUnlessResolvedWithin,创造了一个新的承诺,它有效地“拦截”来自传入函数的承诺。它返回一个复制其解析/拒绝行为的新的,除了它还拒绝了在超时内未解析的promise:
app.factory('failUnlessResolvedWithin', function($q, $timeout) {
return function(func, time) {
var deferred = $q.defer();
$timeout(function() {
deferred.reject('Not resolved within ' + time);
}, time);
$q.when(func()).then(function(results) {
deferred.resolve(results);
}, function(failure) {
deferred.reject(failure);
});
return deferred.promise;
};
});
The tests for these are a bit tricky (and long), but you can see them at http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview . The main points of the tests are
这些测试有点棘手(很长),但你可以在http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview看到它们。测试的要点是
-
The tests for the controller mocks
failUnlessResolvedWithin
with a call to$timeout
.控制器的测试模拟failUnlessResolvedWithin并调用$ timeout。
$provide.value('failUnlessResolvedWithin', function(func, time) { return $timeout(func, time); });
This is possible since 'failUnlessResolvedWithin' is (deliberately) syntactically equivalent to
$timeout
, and done since$timeout
provides theflush
function to test various cases.这是可能的,因为'failUnlessResolvedWithin'(故意)在语法上等同于$ timeout,并且因为$ timeout提供了flush函数来测试各种情况。
-
The tests for the service itself uses calls
$timeout.flush
to test behaviour of the various cases of the original promise being resolved/rejected before/after the timeout.服务本身的测试使用调用$ timeout.flush来测试在超时之前/之后解析/拒绝的原始承诺的各种情况的行为。
beforeEach(function() { failUnlessResolvedWithin(func, 2) .catch(function(error) { failResult = error; }); }); beforeEach(function() { $timeout.flush(3); $rootScope.$digest(); }); it('the failure callback should be called with the error from the service', function() { expect(failResult).toBe('Not resolved within 2'); });
You can see all this in action at http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview
你可以在http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview看到这一切
#3
1
My implementation of @Michal Charemza 's failUnlessResolvedWithin with a real sample. By passing deferred object to the func it reduces having to instantiate a promise in usage code "ByUserPosition". Helps me deal with firefox and geolocation.
我实现了@Michal Charemza的failUnlessResolvedWithin和一个真实的样本。通过将延迟对象传递给func,它减少了必须在使用代码“ByUserPosition”中实例化一个promise。帮助我处理firefox和地理定位。
.factory('failUnlessResolvedWithin', ['$q', '$timeout', function ($q, $timeout) {
return function(func, time) {
var deferred = $q.defer();
$timeout(function() {
deferred.reject('Not resolved within ' + time);
}, time);
func(deferred);
return deferred.promise;
}
}])
$scope.ByUserPosition = function () {
var resolveBy = 1000 * 30;
failUnlessResolvedWithin(function (deferred) {
navigator.geolocation.getCurrentPosition(
function (position) {
deferred.resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude });
},
function (err) {
deferred.reject(err);
}, {
enableHighAccuracy : true,
timeout: resolveBy,
maximumAge: 0
});
}, resolveBy).then(findByPosition, function (data) {
console.log('error', data);
});
};