【问题标题】:Observable mock/spy not consistent in Angular unit test - successful on first call, fails on secondAngular 单元测试中可观察到的模拟/间谍不一致 - 第一次调用成功,第二次失败
【发布时间】:2022-01-13 19:43:55
【问题描述】:

我正在尝试对我编写的服务进行单元测试。大多数测试都通过了,但我的最后一个测试失败了,尽管我的模拟设置与我的一个工作测试相同。

这是我的服务。您会注意到我导入了其他服务来进行 API 调用并创建一个快餐栏。同样在我的服务中,我有逻辑来确定应该调用哪个 API:

showMarkOffline(incident: any): void {
    let name;
    let apiCall;

    if (incident.sessionType === 'network') {
      name = incident.networkName;
      const nodes = (incident.nodes || []).map((node) => node.id);
      apiCall = () => this.hubsApiService.markOffline(incident.id, nodes);
    } else {
      name = (incident && incident.workerName) ? incident.workerName : incident.id;
      const gatewayId = (incident && incident.gatewayId) ? incident.gatewayId : null;
      apiCall = this.phonesApiService.markOffLine(gatewayId, incident.id);
    }

    const dialogRef = this.modalWrapperService.openConfirmDialog('ns.common:markOfflineDialog.title',
      ['ns.common:markOfflineDialog.content', { 0: name }],
      'ns.common:markOfflineDialog.ok',
      'ns.common:cancel');

    dialogRef.afterClosed().subscribe((confirmation: boolean) => {
      if (confirmation) {
        apiCall().subscribe(() => {
        // the second test doesn't seem to get here
          this.snackbarWrapperService
            .openSuccess('ns.common:markOfflineDialog.passed', { 0: name });
        }, () => {
          this.snackbarWrapperService
            .openError('ns.common:markOfflineDialog.failed', { 0: name });
        });
      }
    });
}

这很好用,现在我需要编写单元测试:

describe('IncidentsService', () => {
  let service: IncidentsService;
  const mockHubsApiService = {
    markOffline: jest.fn()
  };

  const mockPhonesApiService = {
    markOffLine: jest.fn()
  };

  const mockModalDialogWrapperService = {
    openConfirmDialog: jest.fn()
  };

  const mockSnackBarWrapperService = {
    openSuccess: jest.fn(),
    openError: jest.fn()
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [{
        provide: HubsApiService,
        useValue: mockHubsApiService
      }, {
        provide: PhonesApiService,
        useValue: mockPhonesApiService
      }, {
        provide: ModalDialogWrapperService,
        useValue: mockModalDialogWrapperService
      }, {
        provide: SnackBarWrapperService,
        useValue: mockSnackBarWrapperService
      }]
    });
    service = TestBed.inject(IncidentsService);
  });

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

  
  describe('showMarkOffline', () => {
    // this test passes!!!! 
    it('should call hubsApiService.markOffline, openConfirmDialog and openSuccess', () => {
      const incident = {
        id: '12345',
        sessionType: 'network',
        networkName: 'RaduNetwork',
        nodes: [{ id: '1', foo: 'bar' }, { id: '2', foo: 'bar' }, { id: '3', foo: 'bar' }, { id: '4', foo: 'bar' }]
      };

      const modalSpy = spyOn(service.modalWrapperService, 'openConfirmDialog').and
        .returnValue({ afterClosed: () => of(true) });
      const apiSpy = spyOn(service.hubsApiService, 'markOffline').and
        .returnValue(of(true));
      const snackBarSpy = spyOn(service.snackbarWrapperService, 'openSuccess');

      service.showMarkOffline(incident);

      expect(modalSpy).toHaveBeenCalledWith('ns.common:markOfflineDialog.title',
        ['ns.common:markOfflineDialog.content', { 0: 'RaduNetwork' }],
        'ns.common:markOfflineDialog.ok',
        'ns.common:cancel');
      expect(apiSpy).toHaveBeenCalledWith('12345', ['1', '2', '3', '4']);
      expect(snackBarSpy).toHaveBeenCalledWith('ns.common:markOfflineDialog.passed', { 0: 'RaduNetwork' });
    });

    // this test fails!
    it('should call phonesApiService.markOffline, openConfirmDialog and openSuccess',() => {
      const incident = {
        id: '12345',
        workerName: 'workerName',
        gatewayId: '54321',
        sessionType: 'whatever'
      };

      const modalSpy = spyOn(service.modalWrapperService, 'openConfirmDialog').and
        .returnValue({ afterClosed: () => of(true) });
      const apiSpy = spyOn(service.phonesApiService, 'markOffLine').and
        .returnValue(of(true));;
      const snackBarSpy = spyOn(service.snackbarWrapperService, 'openSuccess');

      service.showMarkOffline(incident);

      expect(modalSpy).toHaveBeenCalledWith('ns.common:markOfflineDialog.title',
        ['ns.common:markOfflineDialog.content', { 0: 'workerName' }],
        'ns.common:markOfflineDialog.ok',
        'ns.common:cancel');
      expect(apiSpy).toHaveBeenCalledWith('54321', '12345');
      // below is the failing test
      expect(snackBarSpy).toHaveBeenCalledWith('ns.common:markOfflineDialog.passed', { 0: 'workerName' });
    });
  });
});

如您所见,我正在为 API 服务调用设置间谍,并且正在为 API 调用设置返回值。问题在于第二个测试,模拟的服务/间谍确实被调用,但.subscribe 方法似乎没有被执行,因此在第二个测试中我的代码从未进入apiCall().subscribe 回调(请参阅中的注释服务)并且测试在这里失败:

expect(snackBarSpy).toHaveBeenCalledWith('ns.common:markOfflineDialog.passed', { 0: 'workerName' });

出现错误:

Error: expect(spy).toHaveBeenCalledWith(...expected)
Expected: "ns.common:markOfflineDialog.passed", {"0": "workerName"}
Number of calls: 0

我不确定为什么这适用于第一个测试而不是第二个。我尝试过使用ngOnDestroy,我尝试过更改我的间谍和模拟的设置,但似乎没有任何东西可以解决第二个单元测试。

【问题讨论】:

    标签: angular unit-testing jestjs


    【解决方案1】:

    看来测试还可以,但实现不行。

    apiCall = () => this.hubsApiService.markOffline(incident.id, nodes); // passes
    vs
    apiCall = this.phonesApiService.markOffLine(gatewayId, incident.id); // does not.
    

    我相信这段代码在运行时也应该失败,因为在第二种情况下apiCall = someObservable; 你不能只称它为apiCall()

    【讨论】:

    • 先生!我可以吻你!谢谢!
    猜你喜欢
    • 2012-07-31
    • 2018-10-19
    • 2011-10-26
    • 2021-10-07
    • 2020-08-31
    • 1970-01-01
    • 2014-01-27
    • 2020-03-28
    • 2020-09-13
    相关资源
    最近更新 更多