diff options
author | crupest <crupest@outlook.com> | 2019-03-11 00:07:59 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-03-11 00:07:59 +0800 |
commit | 31199282e1ccf72bb464117ae68668aed91e2530 (patch) | |
tree | 0dbce8aa1953d06237e80761440ab21172c95db9 /Timeline | |
parent | e72a1cc3f98e45aee6eb29d3281118fa8373233f (diff) | |
download | timeline-31199282e1ccf72bb464117ae68668aed91e2530.tar.gz timeline-31199282e1ccf72bb464117ae68668aed91e2530.tar.bz2 timeline-31199282e1ccf72bb464117ae68668aed91e2530.zip |
Write unit tests.
Diffstat (limited to 'Timeline')
11 files changed, 165 insertions, 68 deletions
diff --git a/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts b/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts new file mode 100644 index 00000000..1743e615 --- /dev/null +++ b/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts @@ -0,0 +1,66 @@ +import { ParamMap } from '@angular/router'; + +import { Observable, BehaviorSubject } from 'rxjs'; +import { map } from 'rxjs/operators'; + +export interface ParamMapCreator { [name: string]: string | string[]; } + +export class MockActivatedRouteSnapshot { + + private paramMapInternal: ParamMap; + + constructor({ mockParamMap }: { mockParamMap: ParamMapCreator } = { mockParamMap: {} }) { + this.paramMapInternal = { + keys: Object.keys(mockParamMap), + get(name: string): string | null { + const param = mockParamMap[name]; + if (typeof param === 'string') { + return param; + } else if (param instanceof Array) { + if (param.length === 0) { + return null; + } + return param[0]; + } + return null; + }, + getAll(name: string): string[] { + const param = mockParamMap[name]; + if (typeof param === 'string') { + return [param]; + } else if (param instanceof Array) { + return param; + } + return []; + }, + has(name: string): boolean { + return mockParamMap.hasOwnProperty(name); + } + }; + } + + get paramMap(): ParamMap { + return this.paramMapInternal; + } +} + +export class MockActivatedRoute { + + snapshot$ = new BehaviorSubject<MockActivatedRouteSnapshot>(new MockActivatedRouteSnapshot()); + + get paramMap(): Observable<ParamMap> { + return this.snapshot$.pipe(map(snapshot => snapshot.paramMap)); + } + + get snapshot(): MockActivatedRouteSnapshot { + return this.snapshot$.value; + } + + pushSnapshot(snapshot: MockActivatedRouteSnapshot) { + this.snapshot$.next(snapshot); + } + + pushSnapshotWithParamMap(mockParamMap: ParamMapCreator) { + this.pushSnapshot(new MockActivatedRouteSnapshot({mockParamMap})); + } +} diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/mock-internal-user-service.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts index f4a85262..f4a85262 100644 --- a/Timeline/ClientApp/src/app/user/internal-user-service/mock-internal-user-service.ts +++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts diff --git a/Timeline/ClientApp/src/app/user/mock-activated-route.ts b/Timeline/ClientApp/src/app/user/mock-activated-route.ts deleted file mode 100644 index 9e516e83..00000000 --- a/Timeline/ClientApp/src/app/user/mock-activated-route.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ParamMap } from '@angular/router'; - -interface MockActivatedRoute { - snapshot: MockActivatedRouteSnapshot; -} - -interface MockActivatedRouteSnapshot { - paramMap: ParamMap; -} - -export function createMockActivatedRoute(mockParamMap: { [name: string]: string | string[] }): MockActivatedRoute { - return { - snapshot: { - paramMap: { - keys: Object.keys(mockParamMap), - get(name: string): string | null { - const param = mockParamMap[name]; - if (typeof param === 'string') { - return param; - } else if (param instanceof Array) { - if (param.length === 0) { - return null; - } - return param[0]; - } - return null; - }, - getAll(name: string): string[] { - const param = mockParamMap[name]; - if (typeof param === 'string') { - return [param]; - } else if (param instanceof Array) { - return param; - } - return []; - }, - has(name: string): boolean { - return mockParamMap.hasOwnProperty(name); - } - } - } - } -} diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts index ca7c024d..c56e1ed1 100644 --- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts @@ -6,7 +6,7 @@ import { of, Observable } from 'rxjs'; import { delay } from 'rxjs/operators'; import { UserDialogComponent } from './user-dialog.component'; -import { createMockInternalUserService } from '../internal-user-service/mock-internal-user-service'; +import { createMockInternalUserService } from '../internal-user-service/internal-user.service.mock'; import { InternalUserService, UserLoginState } from '../internal-user-service/internal-user.service'; @Component({ diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts index ba015ae6..1efbb5c7 100644 --- a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.spec.ts @@ -1,20 +1,39 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; + +import { MockActivatedRoute } from 'src/app/test-utilities/activated-route.mock'; +import { createMockInternalUserService } from '../internal-user-service/internal-user.service.mock'; import { UserLoginSuccessComponent } from './user-login-success.component'; -import { By } from '@angular/platform-browser'; +import { InternalUserService } from '../internal-user-service/internal-user.service'; + describe('UserLoginSuccessComponent', () => { let component: UserLoginSuccessComponent; let fixture: ComponentFixture<UserLoginSuccessComponent>; + let mockInternalUserService: jasmine.SpyObj<InternalUserService>; + let mockActivatedRoute: MockActivatedRoute; + const mockUserInfo = { username: 'crupest', roles: ['superman', 'coder'] }; beforeEach(async(() => { + mockInternalUserService = createMockInternalUserService(); + mockActivatedRoute = new MockActivatedRoute(); + + // mock currentUserInfo property. because it only has a getter so cast it to any first. + (<any>mockInternalUserService).currentUserInfo = mockUserInfo; + TestBed.configureTestingModule({ - declarations: [UserLoginSuccessComponent] + declarations: [UserLoginSuccessComponent], + providers: [ + { provide: InternalUserService, useValue: mockInternalUserService }, + { provide: ActivatedRoute, useValue: mockActivatedRoute } + ] }) .compileComponents(); })); @@ -22,18 +41,29 @@ describe('UserLoginSuccessComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(UserLoginSuccessComponent); component = fixture.componentInstance; - component.userInfo = mockUserInfo; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); - it('should work well', () => { + it('user info should work well', () => { + fixture.detectChanges(); + + expect((fixture.debugElement.query(By.css('p.login-success-message')))).toBeFalsy(); + expect((fixture.debugElement.query(By.css('span.username')).nativeElement as HTMLSpanElement).textContent) .toBe(mockUserInfo.username); expect((fixture.debugElement.query(By.css('span.roles')).nativeElement as HTMLSpanElement).textContent) .toBe(mockUserInfo.roles.join(', ')); }); + + it('login success message should display well', () => { + mockActivatedRoute.pushSnapshotWithParamMap({ reason: 'login' }); + + fixture.detectChanges(); + + expect((fixture.debugElement.query(By.css('p.login-success-message')))).toBeTruthy(); + }); }); diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts index 3d431ce7..9c9ee1dc 100644 --- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts @@ -1,28 +1,33 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; -import { createMockInternalUserService } from '../internal-user-service/mock-internal-user-service'; -import { createMockActivatedRoute } from '../mock-activated-route'; -import { UserLoginComponent, LoginEvent } from './user-login.component'; +import { of, throwError } from 'rxjs'; + +import { createMockInternalUserService } from '../internal-user-service/internal-user.service.mock'; +import { MockActivatedRoute } from '../../test-utilities/activated-route.mock'; +import { UserLoginComponent } from './user-login.component'; import { InternalUserService } from '../internal-user-service/internal-user.service'; +import { UserInfo } from '../entities'; describe('UserLoginComponent', () => { let component: UserLoginComponent; let fixture: ComponentFixture<UserLoginComponent>; let mockInternalUserService: jasmine.SpyObj<InternalUserService>; + let mockActivatedRoute: MockActivatedRoute; beforeEach(async(() => { mockInternalUserService = createMockInternalUserService(); + mockActivatedRoute = new MockActivatedRoute(); TestBed.configureTestingModule({ declarations: [UserLoginComponent], providers: [ - {provide: InternalUserService, useValue: mockInternalUserService}, - {provide: ActivatedRoute, useValue:} // TODO: custom route snapshot param later. - ] + { provide: InternalUserService, useValue: mockInternalUserService }, + { provide: ActivatedRoute, useValue: mockActivatedRoute } + ], imports: [ReactiveFormsModule], schemas: [NO_ERRORS_SCHEMA] }) @@ -32,14 +37,16 @@ describe('UserLoginComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(UserLoginComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { + fixture.detectChanges(); expect(component).toBeTruthy(); }); it('reactive form should work well', () => { + fixture.detectChanges(); + const usernameInput = fixture.debugElement.query(By.css('input[type=text]')).nativeElement as HTMLInputElement; const passwordInput = fixture.debugElement.query(By.css('input[type=password]')).nativeElement as HTMLInputElement; @@ -56,16 +63,57 @@ describe('UserLoginComponent', () => { }); }); - it('login event should work well', fakeAsync(() => { - let userCredential: LoginEvent; - component.login.subscribe((e: LoginEvent) => { userCredential = e; }); + it('login should work well', () => { fixture.detectChanges(); + const mockValue = { username: 'user', password: 'user' }; + + mockInternalUserService.tryLogin.withArgs(mockValue).and.returnValue(of(<UserInfo>{ username: 'user', roles: ['user'] })); + component.form.setValue(mockValue); component.onLoginButtonClick(); - expect(userCredential).toEqual(mockValue); - })); + + expect(mockInternalUserService.tryLogin).toHaveBeenCalledWith(mockValue); + expect(mockInternalUserService.userRouteNavigate).toHaveBeenCalledWith(['success', { reason: 'login' }]); + }); + + describe('message display', () => { + it('nologin reason should display', () => { + mockActivatedRoute.pushSnapshotWithParamMap({ reason: 'nologin' }); + fixture.detectChanges(); + expect(component.message).toBe('nologin'); + expect((fixture.debugElement.query(By.css('p.mat-body')).nativeElement as + HTMLParagraphElement).textContent).toBe('You haven\'t login.'); + }); + + it('invalid login reason should display', () => { + mockActivatedRoute.pushSnapshotWithParamMap({ reason: 'invalidlogin' }); + fixture.detectChanges(); + expect(component.message).toBe('invalidlogin'); + expect((fixture.debugElement.query(By.css('p.mat-body')).nativeElement as + HTMLParagraphElement).textContent).toBe('Your login is no longer valid.'); + }); + + it('custom error message should display', () => { + const customMessage = 'custom message'; + + fixture.detectChanges(); + + const mockValue = { + username: 'user', + password: 'user' + }; + mockInternalUserService.tryLogin.withArgs(mockValue).and.returnValue(throwError(new Error(customMessage))); + component.form.setValue(mockValue); + component.onLoginButtonClick(); + + fixture.detectChanges(); + expect(component.message).toBe(customMessage); + expect((fixture.debugElement.query(By.css('p.mat-body')).nativeElement as + HTMLParagraphElement).textContent).toBe(customMessage); + }); + }); }); diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts index 082f879c..79a788de 100644 --- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts @@ -1,4 +1,4 @@ -import { Component, Output, OnInit, EventEmitter } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; @@ -6,10 +6,6 @@ import { InternalUserService } from '../internal-user-service/internal-user.serv export type LoginMessage = 'nologin' | 'invalidlogin' | string; -export class LoginEvent { - username: string; - password: string; -} @Component({ selector: 'app-user-login', diff --git a/Timeline/ClientApp/src/app/user/user.module.ts b/Timeline/ClientApp/src/app/user/user.module.ts index 1e70d33d..c399c9e0 100644 --- a/Timeline/ClientApp/src/app/user/user.module.ts +++ b/Timeline/ClientApp/src/app/user/user.module.ts @@ -10,7 +10,7 @@ import { import { UserDialogComponent } from './user-dialog/user-dialog.component'; import { UserLoginComponent } from './user-login/user-login.component'; import { UserLoginSuccessComponent } from './user-login-success/user-login-success.component'; -import { UtilityModule } from '../utility/utility.module'; +import { UtilityModule } from '../utilities/utility.module'; import { RouterModule } from '@angular/router'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; diff --git a/Timeline/ClientApp/src/app/utility/debounce-click.directive.spec.ts b/Timeline/ClientApp/src/app/utilities/debounce-click.directive.spec.ts index 75710d0c..75710d0c 100644 --- a/Timeline/ClientApp/src/app/utility/debounce-click.directive.spec.ts +++ b/Timeline/ClientApp/src/app/utilities/debounce-click.directive.spec.ts diff --git a/Timeline/ClientApp/src/app/utility/debounce-click.directive.ts b/Timeline/ClientApp/src/app/utilities/debounce-click.directive.ts index feb0404e..feb0404e 100644 --- a/Timeline/ClientApp/src/app/utility/debounce-click.directive.ts +++ b/Timeline/ClientApp/src/app/utilities/debounce-click.directive.ts diff --git a/Timeline/ClientApp/src/app/utility/utility.module.ts b/Timeline/ClientApp/src/app/utilities/utility.module.ts index dd686bf7..dd686bf7 100644 --- a/Timeline/ClientApp/src/app/utility/utility.module.ts +++ b/Timeline/ClientApp/src/app/utilities/utility.module.ts |