2014-04-10 8 views
16

Sto provando a impostare un timeout nel mio controller in modo che se una risposta non viene ricevuta in 250 ms, dovrebbe fallire. Ho impostato il mio test unitario per avere un timeout di 10000 in modo che questa condizione sia soddisfatta, qualcuno può indicarmi la giusta direzione? (EDIT Sto cercando di ottenere questo risultato senza utilizzare il servizio $ http che so fornisce funzionalità di timeout)Impostazione di un gestore di timeout su una promessa in angularjs

(EDIT - i miei altri test di unità stavano fallendo perché non stavo chiamando il timeout.flush su di loro, ora ho solo è necessario far partire il messaggio di timeout quando viene restituita una promessa indefinita da promiseService.getPromise(). Ho rimosso il codice iniziale dalla domanda).

promiseService (promessa è una variabile suite di test avermi permesso di usare un comportamento diverso per la promessa in ogni suite di test prima di applicare, ad esempio, respingono in uno, successo in un altro)

mockPromiseService = jasmine.createSpyObj('promiseService', ['getPromise']); 
    mockPromiseService.getPromise.andCallFake(function() { 
     promise = $q.defer(); 
     return promise.promise; 
    }) 

funzione il controllore di quella in fase di 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!"); 
    }); 
} 

di prova (di solito risolvo o rifiutare la promessa qui, ma non impostando esso sto simulando un timeout)

it('Timeout logs promise failure', function(){ 
    spyOn(console, 'log'); 
    scope.qPromiseCall(); 
    $timeout.flush(251); 
    $rootScope.$apply(); 
    expect(console.log).toHaveBeenCalledWith("Timed out"); 
}) 
+0

Potete mostrarci 'promiseService.getPromise()'? –

+0

Non è ancora implementato Sto cercando di farlo per primo, dovrebbe essere collegato all'implementazione del servizio promessa? – SMC

+1

Come puoi dire che non funziona se non lo hai ancora implementato –

risposta

29

In primo luogo, vorrei dire che l'implementazione del controller dovrebbe essere qualcosa di simile:

$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 
    }); 
    } 

i test:

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

Un altro esempio con 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 

    } 
}); 

i test sono simili all'esempio precedente:

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

+0

Grazie purtroppo ho modificato per errore la parte della mia domanda che richiede che questo sia per una promessa non gestita dal servizio $ http poiché so che ha un gestore di timeout incorporato, potresti aggiornare per questo scenario? – SMC

+0

@ LinuxN00b: hai visto il mio secondo esempio? Soddisfa le tue esigenze? –

+0

Grazie per il fatto che sto ancora bevendo il mio caffè mattutino, quindi ho completamente perso la seconda demo. In questo esempio, nella vita reale, come procederesti a gestire il timeout per la promessa del promessa del servizio in un modo che è pulito e impedisce alla promessa di tornare più tardi in un momento inaspettato e innescare un problema? – SMC

8

Il comportamento di "mancanza di una promessa a meno che non si risolve con un periodo di tempo specificato" sembra l'ideale per il refactoring in un servizio/fabbrica separati. Questo dovrebbe rendere più chiaro il codice in entrambi i nuovi servizi/fabbrica e controller e più riutilizzabile.

Il regolatore, che ho pensato appena imposta il successo/insuccesso sul campo di applicazione:

app.controller('MainCtrl', function($scope, failUnlessResolvedWithin, myPromiseService) { 
    failUnlessResolvedWithin(function() { 
    return myPromiseService.getPromise(); 
    }, 250).then(function(result) { 
    $scope.result = result; 
    }, function(error) { 
    $scope.error = error; 
    }); 
}); 

E la fabbrica, failUnlessResolvedWithin, crea una nuova promessa, che di fatto "intercetta" una promessa da un passato in funzione. Esso restituisce uno nuovo che replica la sua volontà/rifiutare comportamenti, se non che respinge anche la promessa, se non è stato risolto entro il timeout:

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; 
    }; 
}); 

I test per questi sono un po 'complicato (e lungo) , ma puoi vederli allo http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview. I punti principali delle prove sono

  • I test per il controller deride failUnlessResolvedWithin con una chiamata a $timeout.

    $provide.value('failUnlessResolvedWithin', function(func, time) { 
        return $timeout(func, time); 
    }); 
    

    Ciò è possibile poiché 'failUnlessResolvedWithin' è (volutamente) sintatticamente equivalente $timeout, e fatto fin $timeout fornisce la funzione flush per verificare vari casi.

  • I test per il servizio stesso utilizzano le chiamate $timeout.flush per verificare il comportamento dei vari casi della promessa originale risolta/rifiutata prima/dopo il timeout.

    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'); 
    }); 
    

si può vedere tutto questo in azione a http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview

+0

Grazie sfortunatamente ho modificato per errore la parte della mia domanda che richiede che questo sia per una promessa non gestita dal servizio $ http poiché so che ha un gestore di timeout incorporato, potresti aggiornare per questo scenario? – SMC

+0

Non credo che la mia risposta richieda nulla in merito al servizio '$ http'. 'GetPromise' può essere una funzione che restituisce una promessa. Puoi chiarire perché pensi che sia così? O se sto fraintendendo, chiarisci le tue esigenze nella domanda. –

+0

La risposta invia il timeout alla chiamata $ http che gestisce il timeout, non è vero? – SMC

1

mia implementazione di failUnlessResolvedWithin @Michal Charemza s' con un campione reale. Passando l'oggetto posticipato al func riduce la necessità di creare un'istanza nel codice di utilizzo "ByUserPosition". Mi aiuta a gestire firefox e geolocalizzazione.

.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); 
       }); 
      }; 
Problemi correlati