fixture.detectchanges() 方法导致测试用例失败
fixture.detectchanges() method resulting in test case to fail
我正在使用 Karma 和 jasmine 对我的组件进行单元测试。 fixture.detechChanges() 应该用于每个测试以检测更改。但是这种方法使测试用例失败并给出错误
错误:InvalidPipeArgument:管道 'Unable to convert "Invalid Date" into a date''DatePipe'
由于我是新手,任何人都可以告诉我为什么会这样以及使用它的正确位置。
在三个测试用例中,第二个测试用例失败了。我做了很多研究,但我不确定为什么我的测试用例失败了。
下面是我的代码
component.ts
export class ResourceOverviewComponent implements OnInit {
id = '--';
lastupdated : any;
creationDate = '--'
description = '--'
tags = '--'
dateTimeFormat = 'MMM d, y h:mm a';
dateTimeZone: any;
constructor(
private readonly resourceOverviewService: ResourceOverviewService,
private readonly utilsService: UtilsService,
private readonly router: Router,
readonly route: ActivatedRoute,
) {}
ngOnInit() {
this.id = this.route.snapshot.queryParamMap.get('id');
this.getDetails();
}
viewInventory(){
this.router.navigate(['..'], {
relativeTo: this.route
});
}
getDetails() {
if (this.id) {
this.getResourceOverview();
}
}
getResourceOverview(): void {
const resourceID = `search_keys=${"resource_id"}&search=${this.id}`
this.resourceId = this.id
this.resourceOverviewService
.getResourceOverview(resourceID)
.subscribe(
(response) => {
const result = response as any;
if (result && result.raw_items[0]) {
this.creationDate = result.raw_resource_created || '--' ;
}
},
(error) => {
console.log('Resource overview details failed with error', error);
}
);
}
}
component.spec.ts
class mockHttpWrapperService {
readonly isApiReady = false;
}
describe('SomeComponent', () => {
let component: SomeComponent;
let fixture: ComponentFixture<SomeComponent>;
let someService: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SomeComponent ],
imports: [
some module,some-module,somemodule.forRoot(),
RouterModule.forRoot([]),
],
providers: [
{ provide: NGXLogger, useClass: MockNGXLogger },
{ provide: HttpWrapperService, useClass: mockHttpWrapperService },
{ provide: Router, useClass: class { navigate = jasmine.createSpy("navigate"); }},
ApiService,
SomeService,
SomeService,
SomeService,
SomeService,
{
provide: ActivatedRoute,
useValue: {
params: of({ id: 'aeb24ca0549c', code: 'IBM' }),
snapshot: {
queryParamMap: convertToParamMap({
id: 'aeb24ca0549c',
code: 'IBM',
})
}
}
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents()
}));
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent);
component = fixture.debugElement.componentInstance;
// fixture.detectChanges();
});
it('should create component ', () => {
expect(component).toBeTruthy();
});
it('should init object for action tabs', async () => {
await fixture.whenStable();
component.ngOnInit();
fixture.detectChanges();
expect(component).toBeTruthy();
});
it('should get resource data ', () => {
someService = TestBed.inject(SomeService);
const data = {some-xyz-object};
spyOn(someService, "getResourceOverview").and.callFake(() => {
return of(data)
})
component.getDetails();
fixture.detectChanges();
expect(component).toBeTruthy();
});
});
component.html
<div class="container-wrapper">
<div class="breadcrumWrapper" >
<ibm-breadcrumb>
<ibm-breadcrumb-item >
{{ id }}
</ibm-breadcrumb-item>
</ibm-breadcrumb>
<div class="breadcrumWrapper" >
</div>
<div>
</div>
</div>
<div class="container-overview" >
<div class="overview" >
</div>
<div class="marginTop ">
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ id }}"
>{{ id }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ resourceType }}"
>{{ resourceType }}</span
>
</div>
</div>
</div>
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ region }}"
>{{ region }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<div *ngFor="let k of keyValue; let i = index;">
<span *ngIf="i!=(keyValue.length-1)"
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ id }}"
>{{ k.Key }} : {{ k.Value }} ,
</span>
<span *ngIf="i==(keyValue.length-1)"
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ id }}"
>{{ k.Key }}:{{ k.Value }}
</span>
</div>
</div>
</div>
</div>
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ correlationID }}"
>{{ correlationID }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ resourceCategory }}"
>{{ resourceCategory }}</span
>
</div>
</div>
</div>
<div *ngIf="showMore">
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ status }}"
>{{ status }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ provider }}"
>{{ provider }}</span
>
</div>
</div>
</div>
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ providerAccount }}"
>{{ providerAccount }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ resourceName }}"
>{{ resourceName }}</span
>
</div>
</div>
</div>
</div>
</div>
<div class="container-overview complex-data-viewer-wrap" >
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ creationDate }}"
>{{ creationDate }}</span
>
</div>
</div>
</div>
</div>
第二次测试
You are calling component.ngOnInit(); which internally calls
getDetails();
第三次测试
You are calling getDetails();
IMO,两者都在做同样的事情,要么你可以删除你的第二次测试并让你的第三次测试调用 component.ngOnInit();使用模拟 api 响应或在第二次测试中具有相同的模拟 API 响应。
问题
问题是您的测试正在构建并向 angular DatePipe
提供无效日期。由于管道存在于您的 HTML 中,您只会在 运行 detectChanges
之后看到错误,因为模板仅在那时呈现。
错误发生在您的 lastUpdated | date:dateTimeFormat
部分
<label class="currenttimestamp">{{ 'discovery_inventory.resource_overview.discovery_lastupdated' | translate }}
{{lastupdated | date : dateTimeFormat}} {{dateTimeZone}}</label>
由于您的 getResourceOverview
在您的测试中执行(由于设置了 ID),因此还将执行以下行:
// This will construct an invalid date, since date is always truthy (even when called with "new Date(undefined)",
// "|| '--'" will never be executed
this.lastupdated = new Date(Date.parse(result.raw_items[0].gpd.discovery_lastupdated)) || '--' ;
这有效地将无效日期(用 new Date(undefined)
构造)分配给您的 lastupdated
实例变量,从而导致您看到的错误。
解决方案
您要么必须确保在测试中提供所有必要的数据 and/or 使您的代码更健壮。在这种情况下,我强烈建议在向您的管道提供数据之前正确检查 undefined/null:
<label class="currenttimestamp">
{{ 'discovery_inventory.resource_overview.discovery_lastupdated' | translate }}
<!--
Use the terniary operator to conditionally use the pipe or fall back
to "--" ("--" itself would be an invalid argument for the pipe, so do
not provide this to the pipe directly!)
-->
{{ (lastupdated ? (lastupdated | date : dateTimeFormat) : '--' }} {{dateTimeZone}}
</label>
此外,如果您无法为变量构造有效日期,则应确保分配未定义的值:
this.lastupdated = result.raw_items[0].gpd.discovery_lastupdated
? new Date(Date.parse(result.raw_items[0].gpd.discovery_lastupdated))
: undefined ;
我正在使用 Karma 和 jasmine 对我的组件进行单元测试。 fixture.detechChanges() 应该用于每个测试以检测更改。但是这种方法使测试用例失败并给出错误
错误:InvalidPipeArgument:管道 'Unable to convert "Invalid Date" into a date''DatePipe'
由于我是新手,任何人都可以告诉我为什么会这样以及使用它的正确位置。
在三个测试用例中,第二个测试用例失败了。我做了很多研究,但我不确定为什么我的测试用例失败了。
下面是我的代码
component.ts
export class ResourceOverviewComponent implements OnInit {
id = '--';
lastupdated : any;
creationDate = '--'
description = '--'
tags = '--'
dateTimeFormat = 'MMM d, y h:mm a';
dateTimeZone: any;
constructor(
private readonly resourceOverviewService: ResourceOverviewService,
private readonly utilsService: UtilsService,
private readonly router: Router,
readonly route: ActivatedRoute,
) {}
ngOnInit() {
this.id = this.route.snapshot.queryParamMap.get('id');
this.getDetails();
}
viewInventory(){
this.router.navigate(['..'], {
relativeTo: this.route
});
}
getDetails() {
if (this.id) {
this.getResourceOverview();
}
}
getResourceOverview(): void {
const resourceID = `search_keys=${"resource_id"}&search=${this.id}`
this.resourceId = this.id
this.resourceOverviewService
.getResourceOverview(resourceID)
.subscribe(
(response) => {
const result = response as any;
if (result && result.raw_items[0]) {
this.creationDate = result.raw_resource_created || '--' ;
}
},
(error) => {
console.log('Resource overview details failed with error', error);
}
);
}
}
component.spec.ts
class mockHttpWrapperService {
readonly isApiReady = false;
}
describe('SomeComponent', () => {
let component: SomeComponent;
let fixture: ComponentFixture<SomeComponent>;
let someService: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SomeComponent ],
imports: [
some module,some-module,somemodule.forRoot(),
RouterModule.forRoot([]),
],
providers: [
{ provide: NGXLogger, useClass: MockNGXLogger },
{ provide: HttpWrapperService, useClass: mockHttpWrapperService },
{ provide: Router, useClass: class { navigate = jasmine.createSpy("navigate"); }},
ApiService,
SomeService,
SomeService,
SomeService,
SomeService,
{
provide: ActivatedRoute,
useValue: {
params: of({ id: 'aeb24ca0549c', code: 'IBM' }),
snapshot: {
queryParamMap: convertToParamMap({
id: 'aeb24ca0549c',
code: 'IBM',
})
}
}
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents()
}));
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent);
component = fixture.debugElement.componentInstance;
// fixture.detectChanges();
});
it('should create component ', () => {
expect(component).toBeTruthy();
});
it('should init object for action tabs', async () => {
await fixture.whenStable();
component.ngOnInit();
fixture.detectChanges();
expect(component).toBeTruthy();
});
it('should get resource data ', () => {
someService = TestBed.inject(SomeService);
const data = {some-xyz-object};
spyOn(someService, "getResourceOverview").and.callFake(() => {
return of(data)
})
component.getDetails();
fixture.detectChanges();
expect(component).toBeTruthy();
});
});
component.html
<div class="container-wrapper">
<div class="breadcrumWrapper" >
<ibm-breadcrumb>
<ibm-breadcrumb-item >
{{ id }}
</ibm-breadcrumb-item>
</ibm-breadcrumb>
<div class="breadcrumWrapper" >
</div>
<div>
</div>
</div>
<div class="container-overview" >
<div class="overview" >
</div>
<div class="marginTop ">
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ id }}"
>{{ id }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ resourceType }}"
>{{ resourceType }}</span
>
</div>
</div>
</div>
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ region }}"
>{{ region }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<div *ngFor="let k of keyValue; let i = index;">
<span *ngIf="i!=(keyValue.length-1)"
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ id }}"
>{{ k.Key }} : {{ k.Value }} ,
</span>
<span *ngIf="i==(keyValue.length-1)"
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ id }}"
>{{ k.Key }}:{{ k.Value }}
</span>
</div>
</div>
</div>
</div>
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ correlationID }}"
>{{ correlationID }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ resourceCategory }}"
>{{ resourceCategory }}</span
>
</div>
</div>
</div>
<div *ngIf="showMore">
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ status }}"
>{{ status }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ provider }}"
>{{ provider }}</span
>
</div>
</div>
</div>
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ providerAccount }}"
>{{ providerAccount }}</span
>
</div>
</div>
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ resourceName }}"
>{{ resourceName }}</span
>
</div>
</div>
</div>
</div>
</div>
<div class="container-overview complex-data-viewer-wrap" >
<div class="bx--row">
<div class="bx--col-xs-6">
<div class="bx--row rowMargins">
<span
class="ellipsis overViewcontent bx--col-xs-5"
title="{{ creationDate }}"
>{{ creationDate }}</span
>
</div>
</div>
</div>
</div>
第二次测试
You are calling component.ngOnInit(); which internally calls getDetails();
第三次测试
You are calling getDetails();
IMO,两者都在做同样的事情,要么你可以删除你的第二次测试并让你的第三次测试调用 component.ngOnInit();使用模拟 api 响应或在第二次测试中具有相同的模拟 API 响应。
问题
问题是您的测试正在构建并向 angular DatePipe
提供无效日期。由于管道存在于您的 HTML 中,您只会在 运行 detectChanges
之后看到错误,因为模板仅在那时呈现。
错误发生在您的 lastUpdated | date:dateTimeFormat
部分
<label class="currenttimestamp">{{ 'discovery_inventory.resource_overview.discovery_lastupdated' | translate }}
{{lastupdated | date : dateTimeFormat}} {{dateTimeZone}}</label>
由于您的 getResourceOverview
在您的测试中执行(由于设置了 ID),因此还将执行以下行:
// This will construct an invalid date, since date is always truthy (even when called with "new Date(undefined)",
// "|| '--'" will never be executed
this.lastupdated = new Date(Date.parse(result.raw_items[0].gpd.discovery_lastupdated)) || '--' ;
这有效地将无效日期(用 new Date(undefined)
构造)分配给您的 lastupdated
实例变量,从而导致您看到的错误。
解决方案
您要么必须确保在测试中提供所有必要的数据 and/or 使您的代码更健壮。在这种情况下,我强烈建议在向您的管道提供数据之前正确检查 undefined/null:
<label class="currenttimestamp">
{{ 'discovery_inventory.resource_overview.discovery_lastupdated' | translate }}
<!--
Use the terniary operator to conditionally use the pipe or fall back
to "--" ("--" itself would be an invalid argument for the pipe, so do
not provide this to the pipe directly!)
-->
{{ (lastupdated ? (lastupdated | date : dateTimeFormat) : '--' }} {{dateTimeZone}}
</label>
此外,如果您无法为变量构造有效日期,则应确保分配未定义的值:
this.lastupdated = result.raw_items[0].gpd.discovery_lastupdated
? new Date(Date.parse(result.raw_items[0].gpd.discovery_lastupdated))
: undefined ;