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

Angular自定义UndoSnackBar组件单元测试问题求助

Angular自定义Snackbar单元测试报错:找不到组件工厂

问题场景

已实现自定义UndoSnackbarComponent,并在TranslationSuggestionReviewModalComponent的showSnackbar方法中调用,但编写单元测试时触发组件工厂找不到的错误。

UndoSnackbarComponent.ts

import { Component } from '@angular/core';
import { MatSnackBarRef } from '@angular/material/snack-bar';

@Component({
  selector: 'app-custom-snackbar',
  templateUrl: './undo-snackbar.component.html'
})

export class UndoSnackbarComponent {
  message!: string;

  constructor(private snackBarRef: MatSnackBarRef<UndoSnackbarComponent>) {}

  onUndo(): void {
    this.snackBarRef.dismissWithAction();
  }

  onDismiss(): void {
    this.snackBarRef.dismiss();
  }
}

TranslationSuggestionReviewModalComponent.ts(调用逻辑)

showSnackbar(): void {
    this.currentSnackbarRef = this.snackBar
      .openFromComponent<UndoSnackbarComponent>(
        UndoSnackbarComponent, {
          duration: COMMIT_TIMEOUT_DURATION,
          verticalPosition: 'bottom',
          horizontalPosition: 'right'
        });
    this.currentSnackbarRef.instance.message = 'Suggestion queued';

    this.currentSnackbarRef.onAction().subscribe(() => {
      this.undoReviewAction();
    });

    this.currentSnackbarRef.afterDismissed().subscribe(() => {
      if (this.hasQueuedSuggestion) {
        this.commitQueuedSuggestion();
      }
    });
  }

单元测试代码片段

class MockMatSnackBarRef {
  instance = { message: '' };
  afterDismissed = () => of({ action: '', dismissedByAction: false });
  onAction = () => of(undefined);
}

beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
        OverlayModule,
        MatSnackBarModule
      ],
      declarations: [TranslationSuggestionReviewModalComponent],
      providers: [
        NgbActiveModal,
        AlertsService,
        ContributionAndReviewService,
        LanguageUtilService,
        SiteAnalyticsService,
        ThreadDataBackendApiService,
        UserService,
        {
          provide: ChangeDetectorRef,
          useValue: changeDetectorRef
        },
        MatSnackBar
      ],
      schemas: [NO_ERRORS_SCHEMA],
    }).compileComponents();
  }));

beforeEach(() => {
  snackBarSpy = spyOn(snackBar, 'openFromComponent').and.returnValue(
        new MockMatSnackBarRef() as unknown as MatSnackBarRef<unknown>
    );
});

报错信息

Chrome Headless 121.0.6167.160 (Mac OS 10.15.7)下测试失败,错误提示:
Error: No component factory found for UndoSnackbarComponent. Did you add it to @NgModule.entryComponents?

解决方法

1. 在测试模块中声明自定义Snackbar组件

Angular测试框架需要知晓所有被引用的组件,哪怕你用了NO_ERRORS_SCHEMA,如果测试逻辑触发了组件实例的创建流程,就必须在测试模块中声明该组件。修改TestBed.configureTestingModule的declarations数组:

declarations: [TranslationSuggestionReviewModalComponent, UndoSnackbarComponent],

如果UndoSnackbarComponent属于独立的功能模块,也可以直接导入该模块到测试模块的imports数组中。

2. 完善Mock对象的方法覆盖

当前的MockMatSnackBarRef缺少组件中调用的dismissWithAction和dismiss方法,补充后避免测试时调用真实方法抛出异常:

class MockMatSnackBarRef {
  instance = { message: '' };
  afterDismissed = () => of({ action: '', dismissedByAction: false });
  onAction = () => of(undefined);
  // 新增mock方法
  dismissWithAction = jasmine.createSpy('dismissWithAction');
  dismiss = jasmine.createSpy('dismiss');
}

3. 确保Spy绑定到正确的实例

确认snackBar实例是从TestBed注入的,避免出现多实例导致Spy失效:

let snackBar: MatSnackBar;
let snackBarSpy: jasmine.Spy;

beforeEach(() => {
  fixture = TestBed.createComponent(TranslationSuggestionReviewModalComponent);
  component = fixture.componentInstance;
  // 从测试床获取SnackBar实例
  snackBar = TestBed.inject(MatSnackBar);
  snackBarSpy = spyOn(snackBar, 'openFromComponent').and.returnValue(
    new MockMatSnackBarRef() as unknown as MatSnackBarRef<UndoSnackbarComponent>
  );
  fixture.detectChanges();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:43:18