From 625c40375fb6ad146a60a8cd25fc30952073d600 Mon Sep 17 00:00:00 2001 From: cv5ch <176032962+cv5ch@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:11:28 +0200 Subject: [PATCH 1/2] Add unittests for AuthComponent --- src/app/auth/auth.component.html | 2 +- src/app/auth/auth.component.spec.ts | 121 ++++++++++++++++++++++++++++ src/app/auth/auth.component.ts | 2 +- 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/app/auth/auth.component.spec.ts diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index 44840fb1d..5e29c4d02 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -54,7 +54,7 @@ - +
diff --git a/src/app/auth/auth.component.spec.ts b/src/app/auth/auth.component.spec.ts new file mode 100644 index 000000000..81e70c2ac --- /dev/null +++ b/src/app/auth/auth.component.spec.ts @@ -0,0 +1,121 @@ +import { asyncScheduler, observeOn, of, throwError } from 'rxjs'; + +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { By } from '@angular/platform-browser'; + +import { AuthService } from '@services/access/auth.service'; +import { AlertService } from '@services/shared/alert.service'; +import { ConfigService } from '@services/shared/config.service'; +import { UnsubscribeService } from '@services/unsubscribe.service'; + +import { AuthComponent } from '@src/app/auth/auth.component'; +import { ButtonsModule } from '@src/app/shared/buttons/buttons.module'; +import { ComponentsModule } from '@src/app/shared/components.module'; +import { InputModule } from '@src/app/shared/input/input.module'; + +describe('AuthComponent', () => { + let component: AuthComponent; + let fixture: ComponentFixture; + + // Mocks + let mockAuthService: jasmine.SpyObj; + let mockAlertService: jasmine.SpyObj; + let mockUnsubscribeService: jasmine.SpyObj; + let mockConfigService: jasmine.SpyObj; + + beforeEach(async () => { + mockAuthService = jasmine.createSpyObj('AuthService', ['logIn']); + mockAlertService = jasmine.createSpyObj('AlertService', ['showErrorMessage']); + mockUnsubscribeService = jasmine.createSpyObj('UnsubscribeService', ['add', 'unsubscribeAll']); + mockConfigService = jasmine.createSpyObj('ConfigService', ['getEndpoint']); + + await TestBed.configureTestingModule({ + imports: [ + ReactiveFormsModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatButtonModule, + InputModule, + ButtonsModule, + ComponentsModule, + MatProgressSpinnerModule + ], + declarations: [AuthComponent], + providers: [ + { provide: AuthService, useValue: mockAuthService }, + { provide: AlertService, useValue: mockAlertService }, + { provide: UnsubscribeService, useValue: mockUnsubscribeService }, + { provide: ConfigService, useValue: mockConfigService } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(AuthComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create the component and initialize form', () => { + expect(component).toBeTruthy(); + expect(component.loginForm).toBeTruthy(); + expect(component.loginForm.controls['username']).toBeTruthy(); + expect(component.loginForm.controls['password']).toBeTruthy(); + }); + + it('should mark form invalid if empty', () => { + component.loginForm.setValue({ username: '', password: '' }); + expect(component.loginForm.invalid).toBeTrue(); + }); + + it('should call authService.logIn on valid form submission via button click', fakeAsync(() => { + const fakeResponse = of({ token: '123' }).pipe(observeOn(asyncScheduler)); + mockAuthService.logIn.and.returnValue(fakeResponse); + + component.loginForm.setValue({ username: 'user', password: 'pass' }); + fixture.detectChanges(); + + const submitButtonDebugEl = fixture.debugElement.query(By.css('button-submit button')); + submitButtonDebugEl.nativeElement.click(); + + expect(component.isLoading).toBeTrue(); + expect(mockAuthService.logIn).toHaveBeenCalledWith('user', 'pass'); + + tick(); + fixture.detectChanges(); + + expect(component.isLoading).toBeFalse(); + expect(component.loginForm.value.username).toBeNull(); + expect(component.loginForm.value.password).toBeNull(); + expect(mockUnsubscribeService.add).toHaveBeenCalled(); + })); + + it('should show error message when login fails on submit button click', fakeAsync(() => { + const errorResponse = throwError(() => new Error('Login failed')).pipe(observeOn(asyncScheduler)); + mockAuthService.logIn.and.returnValue(errorResponse); + + component.loginForm.setValue({ username: 'user', password: 'wrong' }); + + const submitButtonDebugEl = fixture.debugElement.query(By.css('button-submit button')); + submitButtonDebugEl.nativeElement.click(); + + expect(component.isLoading).toBeTrue(); + + tick(); + + expect(component.isLoading).toBeFalse(); + expect(mockAlertService.showErrorMessage).toHaveBeenCalledWith('An error occurred. Please try again later.'); + })); + + it('should call unsubscribeAll on destroy', () => { + component.ngOnDestroy(); + expect(mockUnsubscribeService.unsubscribeAll).toHaveBeenCalled(); + }); +}); diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index 3f80d692e..028d6129e 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -15,8 +15,8 @@ import { LocalStorageService } from '@services/storage/local-storage.service'; import { UnsubscribeService } from '@services/unsubscribe.service'; import { UISettingsUtilityClass } from '@src/app/shared/utils/config'; -import { environment } from '@src/environments/environment'; import { HeaderConfig } from '@src/config/default/app/config.model'; +import { environment } from '@src/environments/environment'; @Component({ selector: 'app-login', From 5288cc0afd3efcc9b587353a86629e8b1c7fa4ef Mon Sep 17 00:00:00 2001 From: cv5ch <176032962+cv5ch@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:22:18 +0200 Subject: [PATCH 2/2] Add check for AuthService be called --- src/app/auth/auth.component.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/auth/auth.component.spec.ts b/src/app/auth/auth.component.spec.ts index 81e70c2ac..5fb22ff6b 100644 --- a/src/app/auth/auth.component.spec.ts +++ b/src/app/auth/auth.component.spec.ts @@ -78,9 +78,7 @@ describe('AuthComponent', () => { it('should call authService.logIn on valid form submission via button click', fakeAsync(() => { const fakeResponse = of({ token: '123' }).pipe(observeOn(asyncScheduler)); mockAuthService.logIn.and.returnValue(fakeResponse); - component.loginForm.setValue({ username: 'user', password: 'pass' }); - fixture.detectChanges(); const submitButtonDebugEl = fixture.debugElement.query(By.css('button-submit button')); submitButtonDebugEl.nativeElement.click(); @@ -89,7 +87,6 @@ describe('AuthComponent', () => { expect(mockAuthService.logIn).toHaveBeenCalledWith('user', 'pass'); tick(); - fixture.detectChanges(); expect(component.isLoading).toBeFalse(); expect(component.loginForm.value.username).toBeNull(); @@ -107,11 +104,12 @@ describe('AuthComponent', () => { submitButtonDebugEl.nativeElement.click(); expect(component.isLoading).toBeTrue(); + expect(mockAuthService.logIn).toHaveBeenCalledWith('user', 'wrong'); tick(); expect(component.isLoading).toBeFalse(); - expect(mockAlertService.showErrorMessage).toHaveBeenCalledWith('An error occurred. Please try again later.'); + expect(mockAlertService.showErrorMessage).toHaveBeenCalled(); })); it('should call unsubscribeAll on destroy', () => {