J'essaie de définir un délai d'expiration dans mon contrôleur afin que, si une réponse n'est pas reçue dans un délai de 250 ms, elle échoue. J'ai configuré mon test d'unité sur un délai d'expiration de 10 000 pour que cette condition soit remplie. Quelqu'un peut-il m'indiquer la bonne direction? (EDIT j'essaye d'y parvenir sans utiliser le service $ http qui, je le sais, fournit une fonctionnalité de délai d'attente)
(EDIT - mes autres tests unitaires échouaient parce que je n’appelais pas timeout.flush sur eux. Maintenant, j’ai juste besoin que le message de délai expire quand une promesse non définie est renvoyée par promiseService.getPromise (). code précoce de la question).
promiseService (promise est une variable de la suite de tests me permettant d'utiliser un comportement différent pour la promesse dans chaque suite de tests avant de l'appliquer, par exemple, rejeter dans l'une, succès dans une autre)
mockPromiseService = jasmine.createSpyObj('promiseService', ['getPromise']);
mockPromiseService.getPromise.andCallFake( function() {
promise = $q.defer();
return promise.promise;
})
Fonction de contrôleur qui est en cours de test -
$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 (normalement, je résous ou rejette la promesse ici, mais en ne la définissant pas, je simule un dépassement de délai)
it('Timeout logs promise failure', function(){
spyOn(console, 'log');
scope.qPromiseCall();
$timeout.flush(251);
$rootScope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
})
Tout d’abord, je voudrais dire que l’implémentation de votre contrôleur devrait ressembler à ceci:
$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
});
}
Vos 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");
})
Un autre exemple avec 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
}
});
Vos tests sont similaires à l'exemple ci-dessus:
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
})
Le comportement consistant à "échouer une promesse à moins que celle-ci ne soit résolue dans un délai déterminé" semble idéal pour la refactorisation dans un service/une usine distincte. Cela devrait rendre le code à la fois dans le nouveau service/usine et dans le contrôleur plus clair et réutilisable.
Le contrôleur, que j'ai supposé juste définit le succès/échec sur la portée:
app.controller('MainCtrl', function($scope, failUnlessResolvedWithin, myPromiseService) {
failUnlessResolvedWithin(function() {
return myPromiseService.getPromise();
}, 250).then(function(result) {
$scope.result = result;
}, function(error) {
$scope.error = error;
});
});
Et l'usine, failUnlessResolvedWithin
, crée une nouvelle promesse, qui "intercepte" efficacement une promesse d'une fonction passée. Il en retourne un nouveau qui reproduit son comportement de résolution/rejet, sauf qu'il rejette également la promesse si elle n'a pas été résolue dans le délai imparti:
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;
};
});
Les tests pour ceux-ci sont un peu difficiles (et longs), mais vous pouvez les voir à http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview . Les points principaux des tests sont
Les tests du contrôleur simulent failUnlessResolvedWithin
avec un appel à $timeout
.
$provide.value('failUnlessResolvedWithin', function(func, time) {
return $timeout(func, time);
});
Cela est possible car 'failUnlessResolvedWithin' est (délibérément) syntaxiquement équivalent à $timeout
et que $timeout
fournit la fonction flush
pour tester divers cas.
Les tests du service lui-même utilisent les appels $timeout.flush
pour tester le comportement des différents cas de résolution/de rejet de la promesse d'origine avant/après le délai d'expiration.
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');
});
Vous pouvez voir tout cela en action à http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview
Mon implémentation de failUnlessResolvedWithin de @Michal Charemza avec un échantillon réel . En transmettant un objet différé à la fonction, cela évite d'avoir à instancier une promesse dans le code d'utilisation "ByUserPosition". M'aide à gérer firefox et la géolocalisation.
.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);
});
};