如何在 Angular 组件中模拟服务功能以进行单元测试

Posted

技术标签:

【中文标题】如何在 Angular 组件中模拟服务功能以进行单元测试【英文标题】:How to mock service function in Angular component for unit test 【发布时间】:2020-02-08 09:29:41 【问题描述】:

我正在为 Angular 应用程序编写单元测试,我正在测试服务函数是否返回值。

component.spec.ts

import TopToolBarService from '../../top-toolbar/top-toolbar.service';

beforeEach(async(() => 
   TestBed.configureTestingModule (
   declarations: [ UsersListComponent],
   providers: [TopToolBarService],//tried mocking service here,still test failed
   schemas:[CUSTOM_ELEMENTS_SCHEMA]
 )
  .compileComponents();
));



beforeEach(() => 
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  );



  it('should return data from service function', async(() => 
    let mockTopToolBarService: jasmine.SpyObj<TopToolBarService>;
    mockTopToolBarService = jasmine.createSpyObj('TopToolBarService', ['getCustomer']);
    mockTopToolBarService.getCustomer.and.returnValue("king");
    fixture.detectChanges();
    expect(component.bDefine).toBe(true); //fails
  ))

component.ts

bDefine = false;
ngOnInit() 
 let customer = this.topToolBarService.getCustomer();
 if (customer == null) 
   bDefine = false;
  else 
    bDefine = true;
   

我相信我已经在我的测试中模拟了服务功能,所以我希望它必须到达变量设置为“真”的其他部分。

TopToolBarService.ts

import  EventEmitter, Injectable, Output  from "@angular/core";

@Injectable()
export class TopToolBarService 
customer = null;

  getCustomer() 
    return this.customer;
  

【问题讨论】:

您能创建一个minimal, reproducible example,最好在 StackBlitz 或类似网站上吗? component.spec.tscomponent.ts 的其余部分是什么? 【参考方案1】:

您必须在运行代码之前配置测试模块。它不知道您的间谍对象,除非您将其作为导入传递给TestBed.configureTestingModule

https://angular.io/guide/testing#component-with-a-dependency

【讨论】:

【参考方案2】:

尝试在 beforeEach(async(() => ...) 中更新提供程序并将您的 mockedService 变量移到它的顶部:

describe('Component TEST', () => 
   ...
   let mockToolBarService;
   ...
      beforeEach(async(() => 
      ...
      mockToolBarService = jasmine.createSpyObj(['getCustomer']);
      mockToolBarService.getCustomer.and.returnValue('king');
      TestBed.configureTestingModule (
           ...
           providers: [  provide: TopToolBarService, useValue: mockToolBarService  ]
           ...

希望对你有帮助!

【讨论】:

【参考方案3】:

更改您的提供者值

beforeEach(() => 
   TestBed.configureTestingModule(
      declarations: [ UsersListComponent],
      providers: [
         provide: TopToolBarService,
         useValue: jasmine.createSpyObj('TopToolBarService', ['getCustomer'])
      ],
      schemas:[CUSTOM_ELEMENTS_SCHEMA]
     );
     mockTopToolBarService = TestBed.get(TopToolBarService);

     mockTopToolBarService.getCustomer.and.returnValue(of([])); // mock output of function
   )

【讨论】:

【参考方案4】:

您可以考虑使用ng-mocks 来避免使用所有样板来配置TestBed

beforeEach(() => MockBuilder(UsersListComponent)
  .mock(TopToolBarService, 
    // adding custom behavior to the service
    getCustomer: jasmine.createSpy().and.returnValue('king'),
  )
);

it('should return data from service function', () => 
  const fixture = MockRender(UsersListComponent);
  // now right after the render the property should be true
  expect(fixtur.point.componentInstance.bDefine).toBe(true);
));

【讨论】:

如何使用 ng-mock 模拟自定义管道,有什么例子吗? MockPipe函数:ng-mocks.sudo.eu/api/MockPipe【参考方案5】:

@Pedro Bezanilla 的回答已经足够好了。鉴于服务是单例的,我认为这是大多数情况下的另一种代码更少的方式。

不同之处在于configureTestingModule 没有提供提供程序配置。间谍是由 spyOn 针对从 TestBed 获取的服务对象设置的。

调用 TestBed.inject 而不是 TestBed.get 是因为它自 v9.0.0 以来已被弃用。

对于单例服务,这里是a doc in Angular website,为了完成,请记住this GitHub issue。

describe('Component TEST', () => 
  ...
  let mockToolBarService: TopToolBarService;
  ...
  beforeEach(waitForAsync(() => 
    TestBed.configureTestingModule(
      declarations: [ UsersListComponent ],
      schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
    );
    mockTopToolBarService = TestBed.inject(TopToolBarService);
    spyOn(mockTopToolBarService, 'getCustomer').and.returnValue(of([]));
  ));
  ...

【讨论】:

以上是关于如何在 Angular 组件中模拟服务功能以进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何检查数据是不是已完成加载以使用异步管道 Angular 8

Angular 2 - 如何以特定顺序在组件中实例化多个服务?

在 Angular 中,如何将组件转换为微服务?

将Angular2服务注入组件基类

Angular 在父子组件中使用多个服务实例

Angular 2 单元测试组件,模拟 ContentChildren