2015-07-21 14 views
10

Sto scrivendo i test dell'unità Jasmine per la mia app in Typescript e li eseguo tramite Resharper. Si suppone di eseguire un'azione se il conduttore lancia un'eccezione:Promise.catch() non rileva eccezioni nel test dell'unità AngularJS

describe("Q Service Test",() => { 
    var q: ng.IQService; 
    var rootScope: ng.IRootScopeService; 

    beforeEach(inject(($q, $rootScope) => { 
     q = $q; 
     rootScope = $rootScope; 
    })); 

    it("Caught exceptions are handled properly",() => { 
     var state = 'ok'; 
     q.when(1) 
      .then(() => { 
       throw new Error("test exception"); 
      }) 
      .catch(() => { 
       state = 'error'; 
      }); 

     rootScope.$digest(); 
     expect(state).toBe('error'); 
    }); 
}); 

Tuttavia, il test è fallito:

Resharper test fail

E 'un po' strano comportamento del mio ambiente di test/strumenti, o sto erroneamente usando il meccanismo di promessa stesso?

risposta

13

Vi sono sicuramente utilizzando il meccanismo di promessa in modo non corretto, gettando un'istruzione throw definita dall'utente non costituisce per la cattura come una promessa di rifiuto in modo corretto. Come indicato nella documentazione $q:

Quando si confrontano deferreds/promesse al comportamento familiare di try/catch/tiro, pensa di rifiutano come tiro parola chiave in JavaScript. Questo significa anche che se si "cattura" un errore tramite un call di errore promessa e si desidera inoltrare l'errore alla promessa derivata da l'attuale promessa, è necessario "ripianificare" l'errore restituendo un rifiuto creato tramite rifiutare.

Sono simili ma non equivalenti, per catturare istruzioni di lancio definite dall'utente, è necessario utilizzare i blocchi di istruzioni catch. Mentre le promesse $q dovrebbero solo promesse rifiutate catch. Quindi, nel tuo caso, restituendo una promessa respinta è il modo corretto di gestire il processo invece di lanciare un'eccezione definita dall'utente.

DEMO

JAVASCRIPT

describe('Q Service Test', function() { 

    var $q, 
     $rootScope; 

    beforeEach(inject(function(_$q_, _$rootScope_) { 
    $q = _$q_; 
    $rootScope = _$rootScope_; 
    })); 

    it('Rejected promises are handled properly', function() { 

    var state = 'ok'; 

    $q.when(1) 
     .then(function() { 
     return $q.reject('rejected'); 
     }) 
     .catch(function() { 
     state = 'error'; 
     }); 

    $rootScope.$digest(); 
    expect(state).toBe('error');  

    }); 

}); 

UPDATE:

Il motivo per cui il codice si comporta in questo modo nel browser, è perché $q implementazione di angolare utilizza l'istruzione try/catch blocchi nell'elaborazione della coda di promessa. Quando uno qualsiasi dei richiami genera errori, rileva l'errore stesso, lo rifiuta con l'eccezione come motivo di rifiuto, successivamente utilizza $exceptionHandler per registrare l'errore. Ti consiglio di restituire semplicemente la promessa respinta.

quanto riguarda il motivo per cui i test di unità agiscono in questo modo è dovuto angular-mocks attuazione del $exceptionHandler è differente con dell'applicazione effettiva $exceptionHandler. Il primo crea un provider con diverse modalità, l'implementazione predefinita di angock-mocks utilizza la modalità rethrow che a sua volta lancia l'eccezione invece di registrarla. Se si desidera che i test unitari si comportino allo stesso modo di come l'applicazione predefinita è $exceptionHandler, è possibile impostare la modalità su 'log'.

DEMO

JAVASCRIPT

describe('Q Service Test', function() { 

    var $q, 
     $rootScope; 

    beforeEach(module('ng', function($exceptionHandlerProvider) { 
    $exceptionHandlerProvider.mode('log'); 
    })); 

    beforeEach(inject(function(_$q_, _$rootScope_) { 
    $q = _$q_; 
    $rootScope = _$rootScope_; 
    })); 

    it('Caught exceptions are handled properly', function() { 

    var state = 'ok'; 

    $q.when(1) 
     .then(function() { 
     throw new Error(); 
     }) 
     .catch(function() { 
     state = 'error'; 
     }); 

    $rootScope.$digest(); 
    expect(state).toBe('error');  

    }); 

}); 
+0

Grazie per la tua risposta dettagliata. Rimane una domanda: come mai la mia versione con 'throw' fallisce il test e funziona come previsto nel browser, con l'eccezione che viene consegnata nel blocco' .catch'? – Impworks

+0

Hai detto che funziona ** come previsto nel browser **, quindi puoi mostrare il codice per quella parte? – ryeballar

+0

ovviamente: https://jsfiddle.net/eqf54zzm/ – Impworks

1

Il post del blog Using and chaining promises in AngularJS indica che quando si esegue il lancio e l'eccezione "si attiva anche il gestore eccezioni registrato di Angular". Quindi la mia ipotesi è che Jasmine stia usando il gestore delle eccezioni di Angular per ascoltare le eccezioni.

Avete bisogno di un'eccezione o hai potuto fare qualcosa di simile:

return q.reject(new Error("test exception")); 
+0

La versione con esplicito 'q.reject' funziona come previsto. Tuttavia, l'attuale modello testato è indipendente dal framework e non si basa su servizi Angular, ma solo sull'interfaccia di promessa. Preferirei una soluzione senza questa dipendenza. – Impworks