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

Angular Jasmine单元测试配置两个Spy Object仅单个生效如何解决

问题原因

报错核心是创建recipeSpy(RecipeService的mock对象)时,未将addIngredientsToShoppingList方法加入spy的方法声明列表,导致注入的mock RecipeService实例上不存在该方法,调用时自然抛出TypeError: service.addIngredientsToShoppingList is not a function错误。

注释掉任意一个provider配置后单测试能通过的逻辑非常明确:

  • 注释RecipeService的mock配置时,TestBed会自动实例化真实的RecipeService,真实实例自带addIngredientsToShoppingList方法,且能正确注入你mock的ShoppingListService,因此第二个测试可通过,但此时拿不到RecipeService的spy对象,第一个测试无法通过。
  • 注释ShoppingListService的mock配置时,注入的是手动创建的mock RecipeService,上面只有你声明的3个spy方法,第一个测试可通过,但mock对象内部没有注入slService依赖,调用addIngredientsToShoppingList时会触发内部依赖找不到的错误。

额外注意:jasmine.createSpyObj生成的是完全独立的空对象,不会继承原服务类的任何方法、属性,所有测试中会调用的方法、访问的属性都必须手动在参数中声明,漏写就会出现方法不存在的报错。

修复方案

根据你的测试场景二选一即可:

场景1:测试对象是RecipeService本身(匹配你当前的测试用例逻辑)

你当前的测试是直接调用RecipeService的方法做验证,属于RecipeService的单元测试,这种场景不需要把RecipeService整体替换为spyObj,只需要mock它的依赖项ShoppingListService即可,RecipeService本身用TestBed生成的真实实例,需要监视某个方法时用spyOn挂载即可。
完整配置代码:

let mockShoppingListServiceSpy: jasmine.SpyObj<ShoppingListService>;
let service: RecipeService;

beforeEach(() => {
  // 仅mock被测试服务的依赖
  mockShoppingListServiceSpy = jasmine.createSpyObj('ShoppingListService', ['addIngredients']);

  TestBed.configureTestingModule({
    providers: [
      RecipeService, // 提供真实的RecipeService作为测试对象
      {provide: ShoppingListService, useValue: mockShoppingListServiceSpy}
    ],
  });
  service = TestBed.inject(RecipeService);
});

对应测试用例调整:

it('should get the recipe per the index', () => {
  // 在真实服务实例上挂载spy监视方法调用
  spyOn(service, 'getRecipe').and.returnValue(mockRecipes[1]);
  service.getRecipe(1);
  expect(service.getRecipe).toHaveBeenCalledWith(1);
});

it('should add ingredients to the shopping list', () => {
  service.addIngredientsToShoppingList(mockIngredients);
  expect(mockShoppingListServiceSpy.addIngredients).toHaveBeenCalledWith(mockIngredients);
});

场景2:测试对象是组件/其他类,RecipeService是依赖项

如果你是在测试组件等其他类,需要全量mock两个依赖服务,只要在创建RecipeService的spy时,把所有测试中会调用的方法全部加入方法声明列表即可:

// 所有用到的RecipeService方法都要加到第二个参数的数组中,不要遗漏
let recipeSpy = jasmine.createSpyObj('RecipeService',
  ['setRecipes', 'getRecipes', 'getRecipe', 'addIngredientsToShoppingList'],
  {recipesChanged: of(mockRecipes), recipes: mockRecipes}
);

let mockShoppingListServiceSpy = jasmine.createSpyObj('ShoppingListService',
  ['addIngredients']
);

beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [/* 你要测试的组件放在这里 */],
    providers: [
      {provide: RecipeService, useValue: recipeSpy},
      {provide: ShoppingListService, useValue: mockShoppingListServiceSpy}
    ],
  }).compileComponents();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:48:18