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

Angular Snackbar服务Jasmine测试报错:MatSnackBar无提供者求助

解决Jasmine测试SnackbarService时的NullInjectorError问题

你遇到的错误核心原因是:测试模块没有为MatSnackBar和NgZone提供依赖项,你的SnackbarService依赖这两个服务,但测试配置中没有告诉TestBed如何创建它们。下面是两种解决方案,推荐使用第一种(模拟依赖),这是单元测试的最佳实践:

方案1:使用模拟依赖(推荐)

单元测试的核心是隔离要测试的服务——我们不需要测试Angular Material的MatSnackBar本身,只需要验证SnackbarService是否正确调用了它的方法,以及是否在NgZone中执行逻辑。

修改你的snackbar.service.spec.ts文件如下:

import { TestBed } from '@angular/core/testing';
import { SnackbarService } from './snackbar.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import { NgZone } from '@angular/core';

describe('SnackbarService', () => {
  let service: SnackbarService;
  let mockMatSnackBar: jasmine.SpyObj<MatSnackBar>;
  let mockNgZone: jasmine.SpyObj<NgZone>;

  beforeEach(() => {
    // 创建MatSnackBar的模拟对象,仅保留我们需要的`open`方法
    const snackBarSpy = jasmine.createSpyObj('MatSnackBar', ['open']);
    // 创建NgZone的模拟对象,让`run`方法直接执行传入的回调函数
    const ngZoneSpy = jasmine.createSpyObj('NgZone', ['run']);
    ngZoneSpy.run.and.callFake((callback) => callback());

    TestBed.configureTestingModule({
      providers: [
        SnackbarService,
        { provide: MatSnackBar, useValue: snackBarSpy },
        { provide: NgZone, useValue: ngZoneSpy }
      ]
    });

    // 获取服务和模拟对象
    service = TestBed.inject(SnackbarService);
    mockMatSnackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj<MatSnackBar>;
    mockNgZone = TestBed.inject(NgZone) as jasmine.SpyObj<NgZone>;
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should call open method and delegate correctly', () => {
    const testMsg = 'Hello';
    const testAction = 'X';
    const testDuration = 1000;

    // 调用服务的open方法
    service.open(testMsg, testAction, testDuration);

    // 验证服务的open方法被正确调用
    expect(service.open).toHaveBeenCalledWith(testMsg, testAction, testDuration);
    // 验证NgZone.run被触发(确保逻辑在Angular区域内执行)
    expect(mockNgZone.run).toHaveBeenCalled();
    // 验证MatSnackBar的open方法被传入正确参数
    expect(mockMatSnackBar.open).toHaveBeenCalledWith(testMsg, testAction, { duration: testDuration });
  });
});

为什么推荐这种方式?

  • 隔离依赖:测试只关注SnackbarService自身的逻辑,不受第三方组件实现的影响
  • 测试更稳定:不需要加载整个Angular Material模块,测试执行速度更快
  • 验证更细致:可以精准验证依赖服务的调用细节,比如参数是否正确、是否在NgZone中执行

方案2:导入MatSnackBarModule(不推荐,仅作参考)

如果你只是想快速让测试通过,可以直接在测试模块中导入MatSnackBarModule,但这会引入不必要的依赖,不符合单元测试的纯粹性原则:

import { TestBed } from '@angular/core/testing';
import { SnackbarService } from './snackbar.service';
import { MatSnackBarModule } from '@angular/material/snack-bar';

describe('SnackbarService', () => {
  beforeEach(() => TestBed.configureTestingModule({
    imports: [MatSnackBarModule] // 导入Material模块提供依赖
  }));

  it('should be created', () => {
    const service: SnackbarService = TestBed.inject(SnackbarService);
    expect(service).toBeTruthy();
  });

  it('should call open()', () => {
    const service: SnackbarService = TestBed.inject(SnackbarService);
    const spy = spyOn(service, 'open');
    service.open('Hello', 'X', 1000);
    expect(spy).toHaveBeenCalled();
  })
});

这种方式虽然能快速解决错误,但测试会依赖Angular Material的具体实现,不够独立。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 23:27:28