2

J'ai un test d'unité de composant qui ne gère pas un rejet de promesse d'un faux comme je le souhaitais.Angular - Composant de test - Erreur lors du retour de Promise.reject de mock

J'ai cette fonction sur un composant qui envoie des données à addUserToOrganisation et gère la promesse qu'il retourne:

public onSubmit() { 
    this.saveStatus = 'Saving'; 
    this.user = this.prepareSaveUser(); 
    this._userService.addUserToOrganisation(this.user) 
    .then(() => this._router.navigate(['/profile'])) 
    .catch(error => this.reportError(error)); 
    } 

Lors du test de cette composante, j'ai fourni une maquette pour UserService qui espionne la addUserToOrganisation point final et renvoie une promesse quelconque:

mockUserService = jasmine.createSpyObj('mockUserService', ['getOrgId', 'addUserToOrganisation']); 
mockUserService.getOrgId.and.returnValue(Promise.resolve('an id')); 
mockUserService.addUserToOrganisation.and.returnValue(Promise.resolve()); 

Cela fonctionne très bien pour les chemins heureux (de résoudre) - je peux vérifier que this._router.navigate() est appelé et ainsi de suite . Voici le test de réussite pour ce chemin heureux:

it('should navigate to /profile if save is successful', fakeAsync(() => { 
    fixture.detectChanges(); 
    tick(); 
    fixture.detectChanges(); 

    component.userForm.controls['firstName'].setValue('John'); 
    component.userForm.controls['lastName'].setValue('Doe'); 
    component.userForm.controls['email'].setValue('[email protected]'); 
    component.onSubmit(); 

    tick(); 
    fixture.detectChanges(); 
    expect(mockRouter.navigate).toHaveBeenCalledWith(['/profile']); 
    })); 

Cependant, j'ai des difficultés à tester le chemin «triste». Je changerai ma maquette pour retourner un Promise.reject et, bien que j'ai un .catch dans onSubmit, je reçois cette erreur:

Error: Uncaught (in promise): no 

C'est donc source de confusion. Voici mon test pour ce triste chemin. Notez que je change la réponse de l'appel simulé.

it('should show Failed save status if the save function fails', fakeAsync(() => { 
    mockUserService.addUserToOrganisation.and.returnValue(Promise.reject('no')); 
    fixture.detectChanges(); 
    tick(); 
    fixture.detectChanges(); 

    component.userForm.controls['firstName'].setValue('John'); 
    component.userForm.controls['lastName'].setValue('Doe'); 
    component.userForm.controls['email'].setValue('[email protected]'); 
    component.onSubmit(); 

    tick(); 
    fixture.detectChanges(); 

    expect(component.saveStatus).toEqual('Failed! no'); 
    })); 

Quelqu'un a-t-il des idées?

Répondre

2

Les rejets promis sont supposés être interceptés, leur non-respect entraînera une erreur dans l'implémentation de promesse de Zone.js (utilisée dans les applications Angular) et dans d'autres (Chrome, core-js promet polyfill, etc.).

Un rejet devrait être attrapé de manière synchrone afin d'être considéré comme manipulé. Il est garanti de cette façon qu'une erreur sera toujours traitée.

Ceci est géré promesse:

const p = Promise.reject(); 
p.catch(err => console.error(err)); 

Ceci est la promesse non gérée:

const p = Promise.reject(); 
setTimeout(() => { 
    p.catch(err => console.error(err)); 
}, 1000); 

Même si un rejet sera peut-être traité à l'avenir, il n'y a aucun moyen comment la mise en œuvre peut « savoir » à ce sujet , donc une promesse est considérée comme non gérée et l'événement unhandledrejection est déclenché.

Ce qui crée le problème est

tick(); 
fixture.detectChanges(); 

Si tick() est là « juste au cas où » et il n'y a aucune utilité réelle de celui-ci, il ne devrait pas être là en premier lieu. Si cela devrait alors le code doit être changé pour ne pas créer des promesses non gérées:

+0

J'ai essayé de supprimer le 'tick()' et j'ai toujours eu une erreur. Je ne comprends pas très bien comment cela serait inutile? Cependant, changer ma réponse fictive de 'returnValue ...' à 'callFake ...' a fait l'affaire. Je ne pense pas que je comprends à 100% la nuance ici cependant. – dafyddPrys

+0

'callFake' crée une promesse rejetée au moment où il est appelé, donc il est chaîné avec' .catch' lors de l'appel 'component.onSubmit()' et aboutit à la promesse gérée.'returnValue' accepte la promesse rejetée existante qui devrait être enchaînée avec' .catch' immédiatement, mais ce n'est pas le cas - parce que timer est déplacé vers tick suivant avec 'tick()' avant qu'un rejet ne soit intercepté dans 'component.onSubmit()'. L'exemple avec 'setTimeout' montre exactement ce qui se passe là-bas. Cela devrait fonctionner sans 'tick()'. Je ne peux pas dire pourquoi vous obtenez toujours cette erreur sans cela. – estus