You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 13:27:03