Testarea dacă un component se comportă corespunzător atunci când serviciul pe care îl apelează aruncă o excepție în Angular 2


În aplicația mea Angular 2, încerc să fac teste unitare pentru următorul component:

export class LoginComponent implements OnInit {
  invalidCredentials = false;
  unreachableBackend = false;

  constructor(private authService: AuthService) {}

  ngOnInit() {
    this.invalidCredentials = false;
  }

  onSubmit(user: any) {
    this.authService.authenticateUser(<User>user).subscribe((result) => {
      if (!result) {
        this.invalidCredentials = true;
      }
    }, (error) => {
      if (error instanceof InvalidCredentialsError) {
        this.invalidCredentials = true;
      } else {
        this.unreachableBackend = true;
      }
    });
  }
}

Am testat deja cu succes scenariul fericit. Acum aș dori să verific că atunci când authService.authenticateUser() aruncă o eroare, invalidCredentials și unreachableBackend sunt setate corect. Iată ce încerc:

describe('Authentication triggering an error', () => {
  class FakeAuthService {
    authenticateUser(user: User) {
      throw new InvalidCredentialsError();
    }
  }

  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let authService: AuthService;
  let spy: Spy;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [
        {provide: AuthService, useClass: FakeAuthService},
        {provide: TranslateService, useClass: FakeTranslateService}
      ],
      imports: [ FormsModule, TranslateModule, AlertModule ]
    });

    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    authService = fixture.debugElement.injector.get(AuthService);
    spy = spyOn(authService, 'authenticateUser').and.callThrough();
  });

  it('should not log in successfully if authentication fails', () => {
    const user = {username: 'username', password: 'password'};
    component.onSubmit(user);

    expect(authService.authenticateUser).toHaveBeenCalledWith(user);
    expect(spy.calls.count()).toEqual(1, 'authenticateUser should have been called once');
    expect(component.invalidCredentials).toBe(true, 'credentials should be invalid because of the exception');
    expect(component.unreachableBackend).toBe(false, 'backend should be reachable at first');
  });
});

Dar când rulez acest test, obțin următoarea eroare:

js/dist/zone.js:96:0 <- src/test.ts:84144:49 webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:91:27 <- src/test.ts:58940:53 execute@webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:119:0 <- src/test.ts:58968:46 execute@webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/jasmine-patch.js:119:0 <- src/test.ts:58968:46 invokeTask@webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:236:0 <- src/test.ts:84284:42 runTask@webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:136:0 <- src/test.ts:84184:57 drainMicroTaskQueue@webpack:///Users/sarbogast/dev/myproject/frontend/~/zone.js/dist/zone.js:368:0 <- src/test.ts:84416:42 PhantomJS 2.1.1 (Mac OS X 0.0.0): Executed 33 of 38 (1 EȘUAT) (sărit 5) (0.704 sec/0.842 sec)

Deci, evident, există ceva ce nu am înțeles. Ar trebui să menționez faptul că sunt complet nou în testarea unităților JS și oarecum nou în programarea reactivă.

Răspuns #1

Nu poți face asta deoarece eroarea va fi pur și simplu aruncată și va ajunge până la test. Ceea ce trebuie să faci este să permiți utilizatorului să se aboneze cu callback-uri, iar apoi poți apela callback-ul de eroare. De exemplu

class FakeAuthService {
  authenticateUser(user: User) {
    // return this service so user can call subscribe
    return this;
  }

  subscribe(onNext, onError) {
    if (onError) {
      onError(new InvalidCredentialsError());
    }
  }
}

O altă opțiune este să folosești Observable.throw(new InvalidCredentialsError())

authenticateUser(user: User) {
  return Observable.throw(new InvalidCredentialsError());
}

Acest lucru va face ca callback-ul onError al oricărui abonat să fie apelat. Personal, îmi place să folosesc mock-ul care se returnează pe sine. Pot face ca mock-ul să fie mai configurabil pentru a facilita testarea. Vezi linkul de mai jos pentru un exemplu a ceea ce vreau să spun.

Vezi și:

  • Angular 2 - Testarea cazului de eroare cu observabile în servicii