可观察 mock/spy 在 Angular 单元测试中不一致 - 第一次调用成功,第二次失败
Observable mock/spy not consistent in Angular unit test - successful on first call, fails on second
我正在尝试对我编写的服务进行单元测试。大多数测试都通过了,但我的最后一个测试失败了,尽管我的模拟设置与我的一个工作测试相同。
这是我的服务。您会注意到我导入了其他服务来进行 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 调用设置 return 值。问题出在第二次测试中,模拟的 service/spy 确实被调用但是 .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
,我试过更改我的间谍和模拟的设置,但似乎没有任何东西可以修复第二个单元测试。
貌似测试没问题,执行起来不行
apiCall = () => this.hubsApiService.markOffline(incident.id, nodes); // passes
vs
apiCall = this.phonesApiService.markOffLine(gatewayId, incident.id); // does not.
我相信这段代码在运行时也应该会失败,因为在第二种情况下 apiCall = someObservable
;
你不能只称它为 apiCall()
我正在尝试对我编写的服务进行单元测试。大多数测试都通过了,但我的最后一个测试失败了,尽管我的模拟设置与我的一个工作测试相同。
这是我的服务。您会注意到我导入了其他服务来进行 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 调用设置 return 值。问题出在第二次测试中,模拟的 service/spy 确实被调用但是 .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
,我试过更改我的间谍和模拟的设置,但似乎没有任何东西可以修复第二个单元测试。
貌似测试没问题,执行起来不行
apiCall = () => this.hubsApiService.markOffline(incident.id, nodes); // passes
vs
apiCall = this.phonesApiService.markOffLine(gatewayId, incident.id); // does not.
我相信这段代码在运行时也应该会失败,因为在第二种情况下 apiCall = someObservable
;
你不能只称它为 apiCall()