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

如何为含BehaviorSubject的RoleService创建Mock解决测试报错

Fixing roleService.isSiteAdmin$ is undefined in Angular Component Tests

The error you're seeing happens because your RoleServiceMock doesn't properly replicate the public API of the real RoleService—specifically, it's missing all the observable properties like isSiteAdmin$ that your MainPageComponent subscribes to in its constructor.

Let's build a comprehensive mock that fully mimics the RoleService behavior, which you can reuse across all components that depend on this service.

Step 1: Create a Complete RoleServiceMock

This mock will replicate all the BehaviorSubject-backed observables from the original service, plus helper methods to dynamically adjust role values during tests (for more control over test scenarios):

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable()
export class RoleServiceMock {
  // Replicate all observable properties from the real RoleService
  private isSiteAdminSubject = new BehaviorSubject<boolean>(false);
  public isSiteAdmin$ = this.isSiteAdminSubject.asObservable();

  private isEditorSubject = new BehaviorSubject<boolean>(false);
  public isEditor$ = this.isEditorSubject.asObservable();

  private isUserSubject = new BehaviorSubject<boolean>(true);
  public isUser$ = this.isUserSubject.asObservable();

  private isProjectApproverSubject = new BehaviorSubject<boolean>(false);
  public isProjectApprover$ = this.isProjectApproverSubject.asObservable();

  private isUsecaseApproverSubject = new BehaviorSubject<boolean>(false);
  public isUsecaseApprover$ = this.isUsecaseApproverSubject.asObservable();

  private isActorApproverSubject = new BehaviorSubject<boolean>(false);
  public isActorApprover$ = this.isActorApproverSubject.asObservable();

  private isScenarioApproverSubject = new BehaviorSubject<boolean>(false);
  public isScenarioApprover$ = this.isScenarioApproverSubject.asObservable();

  private isPolicyApproverSubject = new BehaviorSubject<boolean>(false);
  public isPolicyApprover$ = this.isPolicyApproverSubject.asObservable();

  private isFtpApproverSubject = new BehaviorSubject<boolean>(false);
  public isFtpApprover$ = this.isFtpApproverSubject.asObservable();

  private isAdminSubject = new BehaviorSubject<boolean>(false);
  public isAdmin$ = this.isAdminSubject.asObservable();

  // Mock the original service's methods
  resetRoles(): void {
    // Reset all role subjects to false (match original service behavior)
    this.isSiteAdminSubject.next(false);
    this.isEditorSubject.next(false);
    this.isUserSubject.next(false);
    this.isProjectApproverSubject.next(false);
    this.isUsecaseApproverSubject.next(false);
    this.isActorApproverSubject.next(false);
    this.isScenarioApproverSubject.next(false);
    this.isPolicyApproverSubject.next(false);
    this.isFtpApproverSubject.next(false);
    this.isAdminSubject.next(false);
  }

  setUserRoles(): void {
    // Leave this empty by default—we'll control role values manually in tests
    // If you need to replicate the original logic (e.g., reading from localStorage), you can add it here
  }

  // Helper methods to dynamically set role values during tests
  setIsSiteAdmin(value: boolean): void {
    this.isSiteAdminSubject.next(value);
  }

  setIsEditor(value: boolean): void {
    this.isEditorSubject.next(value);
  }

  setIsUser(value: boolean): void {
    this.isUserSubject.next(value);
  }

  // Add similar helper methods for all other roles as needed
}

Step 2: Update Your Test Configuration

You already have the mock referenced in your TestBed providers, but make sure you're using the new complete mock. Then, you can inject the mock instance into your tests to control role values:

let component: MainPageComponent;
let fixture: ComponentFixture<MainPageComponent>;
let roleServiceMock: RoleServiceMock; // Add this to hold the mock instance

fdescribe('MainPageComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        // ... your existing declarations
      ],
      providers: [
        // ... your existing providers
        { provide: RoleService, useClass: RoleServiceMock }, // Ensure this points to your new mock
      ],
      imports: [
        // ... your existing imports
      ],
      schemas: [
        NO_ERRORS_SCHEMA,
        CUSTOM_ELEMENTS_SCHEMA
      ]
    }).compileComponents();
  }));

  beforeEach(async(() => {
    fixture = TestBed.createComponent(MainPageComponent);
    component = fixture.componentInstance;
    // Get the mock instance from the TestBed
    roleServiceMock = TestBed.inject(RoleService) as unknown as RoleServiceMock;
    fixture.detectChanges(); // Trigger initial change detection
  }));

  it('should create the app', () => {
    expect(component).toBeTruthy();
  });

  // Example test: Verify menu options are enabled for site admins
  it('should enable other menu options when user is a site admin', () => {
    // Set the role value
    roleServiceMock.setIsSiteAdmin(true);
    // Trigger change detection to update the component's subscriptions
    fixture.detectChanges();
    // Assert the expected behavior
    expect(component.isOtherMenuOptionDisabled).toBe(false);
  });
});

Key Notes

  • Mock Completeness: Always ensure your mock replicates every public property/method that components use from the real service. Missing properties will throw undefined errors, just like you saw.
  • Controllable Tests: Using helper methods like setIsSiteAdmin lets you easily test different role scenarios without relying on localStorage or other external dependencies.
  • Reusability: This mock works for any component that depends on RoleService—just add it to the component's test providers and use the helper methods to adjust roles as needed.

内容的提问来源于stack exchange,提问作者tathai96

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:10:42