尝试监视在 ngOnInit 内部调用的服务方法时,未达到 Jasmine 间谍 callFake
Jasmine spy callFake not being reached when trying spy on a service method that is called inside ngOnInit
我正在尝试为使用服务执行 HTTP 请求以从数据库检索信息的组件设置一些单元测试。我对 angular 和单元测试还很陌生,所以请耐心等待。在我的测试中,我试图监视名为 getClients
的函数(此函数本质上是实际执行 HTTP 请求的服务的处理程序)并使用 callFake
.[=26= 调用假函数]
我 运行 遇到的问题是 getClients
函数没有被覆盖,这让我相信间谍不起作用或没有监视我认为的东西。我可以看出它没有被调用,因为失败消息引用了真实 getClients
函数中的内容。
测试代码:
我的理解是,因为我试图监视的函数在 ngOnInit
函数中,所以我必须先定义监视,然后实例化组件。我也试过 运行 it
里面的间谍,但也没用。
describe('handleID', () => {
beforeEach(waitForAsync (() => {
spyOn(service, 'getClients').and.callFake(() => {
let companyList = [
{
COMPANYDESCRIPTOR: "Hello World Inc.",
COMPANYNAME: "Hello World Inc.",
CUSTOMERID: "abcdef123456",
CUSTOMERKEY: 123456
}
]
component.companySearchService.companies.next(companyList);
return companyList;
});
fixture = TestBed.createComponent(CompanySearchComponent);
component = fixture.componentInstance;
service = component.companySearchService;
fixture.detectChanges();
}));
it("should update component.companyForm.controls['selectedCompany'] to 'Hello World Inc.'", () => {
component.companyForm = component._formBuilder.group({
selectedCompany: ['']
})
component.pathToNameProp = 'COMPANYNAME';
component.pathToIdProp = ['CUSTOMERID', 'CUSTOMERKEY'];
let id = 123456;
component.handleID(id);
expect(component.companyForm.get('selectedCompany')).toBe('Hello World Inc.');
})
})
实际函数:
为了清楚起见,我在下面提供了 getClients
函数。 dbService
是进行 API 调用的数据库服务。 makeAnApiCall
returns observable
和 subscribe
只是将数据传递给另一个处理程序,该处理程序根据 source
.[=26 确定如何处理数据=]
getClients(endpoint, method, options = []) {
this.loading.next(true);
this.dbService
.makeAnApiCall(endpoint, method, options)
.subscribe(
res => this.generateCompanyList(res, this.source.getValue())
)
}
失败消息:
失败消息引用了从数据库服务的 makeAnApiCall
方法返回的可见订阅。这让我相信间谍根本没有被创造出来,或者完全在监视其他东西。
Failed: Cannot read properties of undefined (reading 'subscribe')
at CompanySearchService.getClients (http://localhost:9876/_karma_webpack_/main.js:6343:13)
at CompanySearchComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/utilities/company-search/company-search.component.ts:98:39)
...
问题:
- 为什么间谍不起作用?
- 关于单元测试,在处理不需要完全避免的可观察对象、承诺和 HTTP 请求时,是否有更好编写单元测试的方法?
在此先感谢您的帮助!
据我所知,您在创建组件之前缺少 TestBed.configureTestingModule
。这需要声明被测组件并提供类似于普通 ngModule 的服务。最好检查 the Angular testing guide 以了解如何使用它。
在测试模块中,您将在 providers 数组中设置 CompanySearchService,然后您可以使用 TestBed.inject(CompanySearchService)
访问它并模拟您想要的方法。
这个解决方案解决了我的问题,但它仍然没有真正回答我的问题。因此,如果有人可以对此提供更多说明,我很乐意将他们的回复标记为已接受的答案。
单元测试
看起来我调用 spyOn
、fixture.detectChanges
和 component.ngOnInit
的顺序扰乱了服务,因此我出现了 Cannot read properties of undefined
错误。下面更新的代码是完整的单元测试。我创建了 __beforeEach
,所以我不必在嵌套单元测试中重复所有内容。我最终也完全放弃了调用 component.ngOnInit()
,因为它似乎 detectChanges
也能正常工作。
describe('CompanySearchComponent', () => {
let fixture, component, service;
let __beforeEach = () => {
fixture = TestBed.createComponent(CompanySearchComponent);
component = fixture.componentInstance;
service = component.companySearchService;
component.companySource = 'database';
spyOn(service, 'getClients').and.callFake(() => {
let response = { data: { rows:[
{
COMPANYDESCRIPTOR: "Hello World Inc.",
COMPANYNAME: "Hello World Inc.",
CUSTOMERID: "abcdef123456",
CUSTOMERKEY: 123456
}
]}}
component.companySearchService.generateCompanyList(response, 'database');
});
fixture.detectChanges();
}
beforeAll(waitForAsync(__beforeEach));
it ('should initialize the component and the service', () => {
expect(component).toBeDefined();
expect(service).toBeDefined();
})
it ('should initalize pathToNameProp to \'COMPANYNAME\' and pathToProp to [\'CUSTOMERID\', \'CUSTOMERKEY\'] with source set to \'database\'', () => {
expect(component.pathToNameProp).toBe('COMPANYNAME');
expect(component.pathToIdProp).toEqual(['CUSTOMERID', 'CUSTOMERKEY']);
})
it ('should update companies array', () => {
expect(component.companies).toEqual([
{
COMPANYDESCRIPTOR: "Hello World Inc.",
COMPANYNAME: "Hello World Inc.",
CUSTOMERID: "abcdef123456",
CUSTOMERKEY: 123456
}
]);
})
describe('handleID', () => {
beforeAll(waitForAsync(__beforeEach));
it("should update selectedCompany form'", () => {
let id = '123456';
component.handleID(id);
expect(component.companyForm.controls['selectedCompany'].value.COMPANYNAME).toBe('Hello World Inc.');
})
})
})
设置源
它可能不相关,但我想提出另一个我 运行 关注的问题。此组件不是 stand-alone,其他组件将其作为依赖项导入。也就是说,必须显式定义 companySearchComponent.companySource
然后重新初始化组件,因为它的值是在构造函数中定义的。
constructor(
private elem: ElementRef,
public companySearchService: CompanySearchService,
private hierarchyService: HierarchyService,
private _formBuilder: FormBuilder
) {
this.companySource = this.elem.nativeElement.dataset.companySrc;
this.prevCompanySrc = this.companySearchService.source.getValue();
}
构造函数引用源的选择器元素
<company-search [hidden]="!showSearch" id="company-search" data-company-src="database"></company-search>
在 companySearchComponent.ngOnInit
中,源值用于定义 HTTP 请求和响应的一些重要属性。它也用于 companySearchService.getClients()
的初始调用(我最初遇到问题的函数)。
ngOnInit() {
switch(this.companySource) {
case 'database':
...
this.pathToNameProp = 'COMPANYNAME';
this.pathToIdProp = ['CUSTOMERID', 'CUSTOMERKEY'];
break;
...
default:
break;
}
if (!this.companySearchService.companies.value || this.prevCompanySrc !== this.companySource) {
this.companySearchService.source.next(this.companySource);
this.companySearchService.getClients(this.endpoint, this.httpMethod, this.httpOptions);
}
...
}
就像我说的,这并不能完全回答我提出的问题,但它是问题的解决方案,所以如果有人发布更全面、更彻底的解决方案,我很乐意将其标记为已接受的答案。
我正在尝试为使用服务执行 HTTP 请求以从数据库检索信息的组件设置一些单元测试。我对 angular 和单元测试还很陌生,所以请耐心等待。在我的测试中,我试图监视名为 getClients
的函数(此函数本质上是实际执行 HTTP 请求的服务的处理程序)并使用 callFake
.[=26= 调用假函数]
我 运行 遇到的问题是 getClients
函数没有被覆盖,这让我相信间谍不起作用或没有监视我认为的东西。我可以看出它没有被调用,因为失败消息引用了真实 getClients
函数中的内容。
测试代码:
我的理解是,因为我试图监视的函数在 ngOnInit
函数中,所以我必须先定义监视,然后实例化组件。我也试过 运行 it
里面的间谍,但也没用。
describe('handleID', () => {
beforeEach(waitForAsync (() => {
spyOn(service, 'getClients').and.callFake(() => {
let companyList = [
{
COMPANYDESCRIPTOR: "Hello World Inc.",
COMPANYNAME: "Hello World Inc.",
CUSTOMERID: "abcdef123456",
CUSTOMERKEY: 123456
}
]
component.companySearchService.companies.next(companyList);
return companyList;
});
fixture = TestBed.createComponent(CompanySearchComponent);
component = fixture.componentInstance;
service = component.companySearchService;
fixture.detectChanges();
}));
it("should update component.companyForm.controls['selectedCompany'] to 'Hello World Inc.'", () => {
component.companyForm = component._formBuilder.group({
selectedCompany: ['']
})
component.pathToNameProp = 'COMPANYNAME';
component.pathToIdProp = ['CUSTOMERID', 'CUSTOMERKEY'];
let id = 123456;
component.handleID(id);
expect(component.companyForm.get('selectedCompany')).toBe('Hello World Inc.');
})
})
实际函数:
为了清楚起见,我在下面提供了 getClients
函数。 dbService
是进行 API 调用的数据库服务。 makeAnApiCall
returns observable
和 subscribe
只是将数据传递给另一个处理程序,该处理程序根据 source
.[=26 确定如何处理数据=]
getClients(endpoint, method, options = []) {
this.loading.next(true);
this.dbService
.makeAnApiCall(endpoint, method, options)
.subscribe(
res => this.generateCompanyList(res, this.source.getValue())
)
}
失败消息:
失败消息引用了从数据库服务的 makeAnApiCall
方法返回的可见订阅。这让我相信间谍根本没有被创造出来,或者完全在监视其他东西。
Failed: Cannot read properties of undefined (reading 'subscribe')
at CompanySearchService.getClients (http://localhost:9876/_karma_webpack_/main.js:6343:13)
at CompanySearchComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/utilities/company-search/company-search.component.ts:98:39)
...
问题:
- 为什么间谍不起作用?
- 关于单元测试,在处理不需要完全避免的可观察对象、承诺和 HTTP 请求时,是否有更好编写单元测试的方法?
在此先感谢您的帮助!
据我所知,您在创建组件之前缺少 TestBed.configureTestingModule
。这需要声明被测组件并提供类似于普通 ngModule 的服务。最好检查 the Angular testing guide 以了解如何使用它。
在测试模块中,您将在 providers 数组中设置 CompanySearchService,然后您可以使用 TestBed.inject(CompanySearchService)
访问它并模拟您想要的方法。
这个解决方案解决了我的问题,但它仍然没有真正回答我的问题。因此,如果有人可以对此提供更多说明,我很乐意将他们的回复标记为已接受的答案。
单元测试
看起来我调用 spyOn
、fixture.detectChanges
和 component.ngOnInit
的顺序扰乱了服务,因此我出现了 Cannot read properties of undefined
错误。下面更新的代码是完整的单元测试。我创建了 __beforeEach
,所以我不必在嵌套单元测试中重复所有内容。我最终也完全放弃了调用 component.ngOnInit()
,因为它似乎 detectChanges
也能正常工作。
describe('CompanySearchComponent', () => {
let fixture, component, service;
let __beforeEach = () => {
fixture = TestBed.createComponent(CompanySearchComponent);
component = fixture.componentInstance;
service = component.companySearchService;
component.companySource = 'database';
spyOn(service, 'getClients').and.callFake(() => {
let response = { data: { rows:[
{
COMPANYDESCRIPTOR: "Hello World Inc.",
COMPANYNAME: "Hello World Inc.",
CUSTOMERID: "abcdef123456",
CUSTOMERKEY: 123456
}
]}}
component.companySearchService.generateCompanyList(response, 'database');
});
fixture.detectChanges();
}
beforeAll(waitForAsync(__beforeEach));
it ('should initialize the component and the service', () => {
expect(component).toBeDefined();
expect(service).toBeDefined();
})
it ('should initalize pathToNameProp to \'COMPANYNAME\' and pathToProp to [\'CUSTOMERID\', \'CUSTOMERKEY\'] with source set to \'database\'', () => {
expect(component.pathToNameProp).toBe('COMPANYNAME');
expect(component.pathToIdProp).toEqual(['CUSTOMERID', 'CUSTOMERKEY']);
})
it ('should update companies array', () => {
expect(component.companies).toEqual([
{
COMPANYDESCRIPTOR: "Hello World Inc.",
COMPANYNAME: "Hello World Inc.",
CUSTOMERID: "abcdef123456",
CUSTOMERKEY: 123456
}
]);
})
describe('handleID', () => {
beforeAll(waitForAsync(__beforeEach));
it("should update selectedCompany form'", () => {
let id = '123456';
component.handleID(id);
expect(component.companyForm.controls['selectedCompany'].value.COMPANYNAME).toBe('Hello World Inc.');
})
})
})
设置源
它可能不相关,但我想提出另一个我 运行 关注的问题。此组件不是 stand-alone,其他组件将其作为依赖项导入。也就是说,必须显式定义 companySearchComponent.companySource
然后重新初始化组件,因为它的值是在构造函数中定义的。
constructor(
private elem: ElementRef,
public companySearchService: CompanySearchService,
private hierarchyService: HierarchyService,
private _formBuilder: FormBuilder
) {
this.companySource = this.elem.nativeElement.dataset.companySrc;
this.prevCompanySrc = this.companySearchService.source.getValue();
}
构造函数引用源的选择器元素
<company-search [hidden]="!showSearch" id="company-search" data-company-src="database"></company-search>
在 companySearchComponent.ngOnInit
中,源值用于定义 HTTP 请求和响应的一些重要属性。它也用于 companySearchService.getClients()
的初始调用(我最初遇到问题的函数)。
ngOnInit() {
switch(this.companySource) {
case 'database':
...
this.pathToNameProp = 'COMPANYNAME';
this.pathToIdProp = ['CUSTOMERID', 'CUSTOMERKEY'];
break;
...
default:
break;
}
if (!this.companySearchService.companies.value || this.prevCompanySrc !== this.companySource) {
this.companySearchService.source.next(this.companySource);
this.companySearchService.getClients(this.endpoint, this.httpMethod, this.httpOptions);
}
...
}
就像我说的,这并不能完全回答我提出的问题,但它是问题的解决方案,所以如果有人发布更全面、更彻底的解决方案,我很乐意将其标记为已接受的答案。