Angular单元测试中如何Mock StorageService响应?
如何在Angular单元测试中Mock StorageService以模拟管理员登录场景
我在组件的ngOnInit()方法中通过StorageService检查session storage,判断用户状态及账户类型:
- 若账户类型为0,将
isAdmin布尔值设为true - 若
isLoggedIn()返回false,将isGuest设为true
组件代码
import { Component, OnInit } from '@angular/core'; import { StorageService } from '../_services/storage.service'; import { AuthService } from '../_services/auth.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-navbar', templateUrl: './navbar.component.html', styleUrl: './navbar.component.css' }) export class NavbarComponent implements OnInit { isLoggedIn = false; isAdmin = false; isGuest = true; constructor(private authService: AuthService, private storageService: StorageService, private router: Router) { } ngOnInit(): void { if (this.storageService.isLoggedIn()) { this.isLoggedIn = true; this.isGuest = false; if (this.storageService.getAccountType() == 0) { this.isAdmin = true; } } } logout(): void { sessionStorage.clear(); this.reloadPage(); } search(text: string): void { this.router.navigate(['/library', {search: text}]) } reloadPage(): void { window.location.reload(); } }
StorageService代码
import { Injectable } from '@angular/core'; const USER_KEY = 'auth-user'; @Injectable({ providedIn: 'root' }) export class StorageService { constructor() { } clean(): void { window.sessionStorage.clear(); } public saveUser(user: any): void { window.sessionStorage.removeItem(USER_KEY); window.sessionStorage.setItem(USER_KEY, JSON.stringify(user)); } public getUser(): any { const user = window.sessionStorage.getItem(USER_KEY); if (user) { return JSON.parse(user); } return {}; } public isLoggedIn(): boolean { const user = window.sessionStorage.getItem(USER_KEY); if (user) { return true; } return false; } public getAccountType() { const user = window.sessionStorage.getItem(USER_KEY); if (user) { var parsedUser = JSON.parse(user); return parsedUser.accountType; } } }
问题
我首次使用Jasmine进行单元测试,想要MockngOnInit()中调用的isLoggedIn()和getAccountType()方法的返回值,以此模拟管理员账户类型,但Mock服务时遇到困难。
当前尝试的单元测试代码
import { FormsModule } from "@angular/forms"; import { NavbarComponent } from "./navbar.component"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { provideHttpClient } from "@angular/common/http"; import { StorageService } from "../_services/storage.service"; describe('NavbarComponent', () => { let component: NavbarComponent; let fixture: ComponentFixture<NavbarComponent>; let mockStorageService: jasmine.SpyObj<StorageService>; beforeEach(async () => { mockStorageService = jasmine.createSpyObj('StorageService', ['isLoggedIn', 'getAccountType']); mockStorageService.isLoggedIn.and.returnValue(true); mockStorageService.getAccountType.and.returnValue(0); await TestBed.configureTestingModule({ declarations: [NavbarComponent], imports: [FormsModule], providers: [{ provide: StorageService, useValue: mockStorageService }] }) .compileComponents(); fixture = TestBed.createComponent(NavbarComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should set isLoggedIn to true on init', () => { component.ngOnInit(); expect(component.isLoggedIn).toBe(true); }); it('should detect admin', () => { component.ngOnInit(); expect(component.isAdmin).toBe(true); }); });
解决方案
你的测试思路是对的,但存在几个小问题需要修正,同时可以补充多场景测试让覆盖更全面:
1. 修正核心问题
- 移除测试用例中手动调用的
component.ngOnInit():fixture.detectChanges()会自动触发组件的初始化流程(包括ngOnInit),重复调用会导致逻辑执行两次,可能干扰断言结果。 - 补充缺失的依赖Mock:组件依赖
AuthService和Router,需要创建对应的SpyObj避免测试报错。
2. 修正后的完整测试代码
import { FormsModule } from "@angular/forms"; import { NavbarComponent } from "./navbar.component"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { StorageService } from "../_services/storage.service"; import { AuthService } from "../_services/auth.service"; import { Router } from "@angular/router"; describe('NavbarComponent', () => { let component: NavbarComponent; let fixture: ComponentFixture<NavbarComponent>; let mockStorageService: jasmine.SpyObj<StorageService>; let mockAuthService: jasmine.SpyObj<AuthService>; let mockRouter: jasmine.SpyObj<Router>; beforeEach(async () => { // 创建所有依赖的SpyObj mockStorageService = jasmine.createSpyObj('StorageService', ['isLoggedIn', 'getAccountType']); mockAuthService = jasmine.createSpyObj('AuthService', []); // 根据实际用到的方法补充 mockRouter = jasmine.createSpyObj('Router', ['navigate']); await TestBed.configureTestingModule({ declarations: [NavbarComponent], imports: [FormsModule], providers: [ { provide: StorageService, useValue: mockStorageService }, { provide: AuthService, useValue: mockAuthService }, { provide: Router, useValue: mockRouter } ] }).compileComponents(); fixture = TestBed.createComponent(NavbarComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeTruthy(); }); // 场景1:管理员用户登录 it('should set isLoggedIn and isAdmin to true when admin user logs in', () => { // 设置Mock返回值 mockStorageService.isLoggedIn.and.returnValue(true); mockStorageService.getAccountType.and.returnValue(0); // 触发初始化 fixture.detectChanges(); // 断言结果 expect(component.isLoggedIn).toBeTrue(); expect(component.isAdmin).toBeTrue(); expect(component.isGuest).toBeFalse(); // 验证方法调用次数 expect(mockStorageService.isLoggedIn).toHaveBeenCalled(); expect(mockStorageService.getAccountType).toHaveBeenCalled(); }); // 场景2:普通用户登录 it('should set isLoggedIn to true but isAdmin to false for regular user', () => { mockStorageService.isLoggedIn.and.returnValue(true); mockStorageService.getAccountType.and.returnValue(1); // 非管理员账户类型 fixture.detectChanges(); expect(component.isLoggedIn).toBeTrue(); expect(component.isAdmin).toBeFalse(); expect(component.isGuest).toBeFalse(); }); // 场景3:未登录状态 it('should set isGuest to true when user is not logged in', () => { mockStorageService.isLoggedIn.and.returnValue(false); fixture.detectChanges(); expect(component.isLoggedIn).toBeFalse(); expect(component.isAdmin).toBeFalse(); expect(component.isGuest).toBeTrue(); // 未登录时不应调用getAccountType expect(mockStorageService.getAccountType).not.toHaveBeenCalled(); }); });
关键说明
- SpyObj创建:用
jasmine.createSpyObj创建依赖服务的模拟对象,指定需要Mock的方法名。 - Mock返回值设置:在每个测试用例中单独设置Mock方法的返回值,确保场景隔离。
- 触发初始化:通过
fixture.detectChanges()触发组件的生命周期钩子,无需手动调用ngOnInit。 - 多场景覆盖:测试管理员、普通用户、未登录三种核心场景,确保组件逻辑的正确性。
内容的提问来源于stack exchange,提问作者JordanChester
相关产品推荐
相关产品推荐

