I am unit testing my component using Karma and jasmine. fixture.detechChanges() should be use for every test in order to detect the changes. But this method is making the test case to fail and giving error
Error: InvalidPipeArgument: 'Unable to convert "Invalid Date" into a date' for pipe 'DatePipe'
Since I am new to this can anyone let me know why this is behaving like this and what is the correct place to use this.
Out the three test cases 2nd test case is failing. I have done lot of research but i am not sure why my test case is failing.
below is my code
resource-overview.component.ts
import { ResourceOverviewService } from '../../../../services/detail/resource-overview/resource-overview.service'
import {
Component,
OnInit,
} from '@angular/core';
import { UtilsService } from '../../../../services/common/utils.service';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-resource-overview',
templateUrl: './resource-overview.component.html',
styleUrls: ['./resource-overview.component.scss']
})
export class ResourceOverviewComponent implements OnInit {
uuid = '--';
resourceName = '--';
status = '--';
provider = '--';
providerAccount = '--';
resourceCategory = '--';
resourceType = '--';
region = '--';
overviewTags = '--';
correlationID = '--';
showMore = true;
crn = '--'
lastupdated : any;
providerAccountName = '--'
resourceId = '--'
creationDate = '--'
description = '--'
tags = '--'
dateTimeFormat = 'MMM d, y h:mm a';
lastUpdateTime: any;
dateTimeZone: any;
keyValue : { key: any, value: any }[] = [];
id = '--';
providerLastUpdated = '--';
providerStatus = '--';
breadcrumbs = [];
launchpadURL = `/launchpad`;
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();
// this.route.queryParams.subscribe(params => {
// console.log("---", params)
// this.id = params.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.uuid = result.raw_items[0].uuid || '--';
this.resourceName = result.raw_items[0].gpd.provider_resource_name || '--';
this.status = result.raw_items[0].gpd.status || '--';
this.provider = result.raw_items[0].gpd.provider_code || '--';
this.providerAccountName = result.raw_items[0].gpd.provider_account_name || '--';
this.providerAccount = result.raw_items[0].gpd.provider_account || '--';
this.resourceCategory = result.raw_items[0].gpd.service_category || '--';
this.resourceType = result.raw_items[0].gpd.provider_resource_type || '--';
this.region = result.raw_items[0].gpd.provider_resource_location || '--';
this.keyValue = result.raw_items[0].gpd.tags;
this.crn = result.raw_items[0].gpd.provider_resource_crn || '--';
this.lastupdated = new Date(Date.parse(result.raw_items[0].gpd.discovery_lastupdated)) || '--' ;
this.providerLastUpdated = result.raw_items[0].gpd.provider_resource_lastupdated || '--' ;
this.dateTimeZone = this.utilsService.getLocalBrowserTime(this.lastupdated) || '--' ;
this.dateTimeFormat = this.utilsService.getDateTimeFormat() || '--' ;
this.creationDate = result.raw_items[0].gpd.provider_resource_created || '--' ;
this.correlationID = result.raw_items[0].gpd.correlation_id || '--' ;
this.providerStatus = result.raw_items[0].gpd.provider_status || '--' ;
}
},
(error) => {
console.log('Resource overview details failed with error', error);
}
);
}
}
resource-overview.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HttpWrapperService } from '../../../../services/common/http-wrapper.service';
import { ResourceOverviewComponent } from './resource-overview.component';
import { NGXLogger } from 'ngx-logger';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CarbonComponentsModule } from '@cloudMatrix-CAM/cb-carbon-components-lib';
import { CarbonModule } from '../../../../../carbon.module';
import { ApiContractService, CbTranslationUiLibModule, TranslationService,AuthenticationContractService } from '@cloudMatrix-CAM/cb-common-ui';
import { MockNGXLogger } from '../../../../components/discovery-inventory/list-view/list-view.component.spec';
import { convertToParamMap, Router } from '@angular/router';
import { ApiService } from '../../../../services/common/api.service';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { CbCommonDiscoveryManagementMicroUiService } from '../../../../services/cb-common-discovery-management-micro-ui.service';
import { ActivatedRoute } from "@angular/router";
import { throwError, of, Observable } from 'rxjs';
import { RouterModule } from '@angular/router'
import { ResourceOverviewService } from 'projects/cb-common-discovery-management-micro-ui/src/app/services/detail/resource-overview/resource-overview.service';
class mockHttpWrapperService {
readonly isApiReady = false;
}
describe('ResourceOverviewComponent', () => {
let component: ResourceOverviewComponent;
let fixture: ComponentFixture<ResourceOverviewComponent>;
let resourceOverviewService: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ResourceOverviewComponent ],
imports: [
CarbonComponentsModule,CarbonModule,CbTranslationUiLibModule.forRoot(),
RouterModule.forRoot([]),
],
providers: [
{ provide: NGXLogger, useClass: MockNGXLogger },
{ provide: HttpWrapperService, useClass: mockHttpWrapperService },
{ provide: Router, useClass: class { navigate = jasmine.createSpy("navigate"); }},
ApiService,
ApiContractService,
ResourceOverviewService,
TranslationService,
CbCommonDiscoveryManagementMicroUiService,
{
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(ResourceOverviewComponent);
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 ', () => {
resourceOverviewService = TestBed.inject(ResourceOverviewService);
const data = {"raw_items":[{"uuid":"123456789:i-0105345f167219fbf:vms","gpd":{"version":"0.1","discovery_lastupdated":"2021-09-08T05:36:08Z","correlation_id":"aws-123456789-i-0105345f167219fbf","core_credential_id":["credid"],"provider_code":"aws","provider_account":"123456789","provider_account_name":"accountname","provider_account_details":{"cb84865d-3477-40f3-9131-4462273cfedb":"accountname"},"provider_resource_type":"vms","provider_resource_id":"i-0105345f167219fbf","provider_resource_name":"cd-642","provider_resource_lastupdated":"","provider_resource_created":"2021-08-30T06:12:49Z","provider_resource_crn":"arn:aws:ec2:us-east-1:123456789:vms/i-0105345f167219fbf","provider_resource_location":"us-east-1","provider_status":"running","location_geo_coordinates":{"latitude":38.86726,"longitude":-77.233959},"status":"Active","service_category":"compute","tags":[{"Key":"Name","Value":"cd-642"}],"raw_config_data":null,"extended_data":null}}],"meta":null,"pagination":{"count":1,"total":1,"offset":0,"limit":10,"first":"https://local-core.gravitant.net/discovery/api/inventory/v1/service?limit=10","last":"https://local-core.gravitant.net/discovery/api/inventory/v1/service?limit=10","next":"","prev":""}};
spyOn(resourceOverviewService, "getResourceOverview").and.callFake(() => {
return of(data)
})
component.getDetails();
fixture.detectChanges();
expect(component).toBeTruthy();
});
});
resource-overview.component.html
<div >
<div >
<ibm-breadcrumb>
<ibm-breadcrumb-item href="{{launchpadURL}}">
Portal
</ibm-breadcrumb-item>
<ibm-breadcrumb-item >
<a (click)="viewInventory()">Inventory Resource view</a>
</ibm-breadcrumb-item>
<ibm-breadcrumb-item >
{{ id }}
</ibm-breadcrumb-item>
</ibm-breadcrumb>
<div >
<label >{{ id }}</label>
</div>
<div>
<label >{{ 'discovery_inventory.resource_overview.discovery_lastupdated' | translate }}
{{lastupdated | date : dateTimeFormat}} {{dateTimeZone}}</label>
</div>
</div>
<div >
<div >
<label >{{"discovery_inventory.resource_overview.overview" | translate}}</label>
</div>
<div >
<div >
<div >
<div >
<label >{{"discovery_inventory.resource_overview.id" | translate}}</label>
<span
title="{{ id }}"
>{{ id }}</span
>
</div>
</div>
<div >
<div >
<label >{{"discovery_inventory.resource_overview.resource_type" | translate}}</label>
<span
title="{{ resourceType }}"
>{{ resourceType }}</span
>
</div>
</div>
</div>
<div >
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.region" | translate
}}</label>
<span
title="{{ region }}"
>{{ region }}</span
>
</div>
</div>
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.tag_key_value" | translate
}}</label>
<div *ngFor="let k of keyValue; let i = index;">
<span *ngIf="i!=(keyValue.length-1)"
title="{{ id }}"
>{{ k.Key }} : {{ k.Value }} ,
</span>
<span *ngIf="i==(keyValue.length-1)"
title="{{ id }}"
>{{ k.Key }}:{{ k.Value }}
</span>
</div>
</div>
</div>
</div>
<div >
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.correlation_id" | translate
}}</label>
<span
title="{{ correlationID }}"
>{{ correlationID }}</span
>
</div>
</div>
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.resource_category" | translate
}}</label>
<span
title="{{ resourceCategory }}"
>{{ resourceCategory }}</span
>
</div>
</div>
</div>
<div *ngIf="showMore">
<div >
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.status" | translate
}}</label>
<span
title="{{ status }}"
>{{ status }}</span
>
</div>
</div>
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.provider" | translate
}}</label>
<span
title="{{ provider }}"
>{{ provider }}</span
>
</div>
</div>
</div>
<div >
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.provider_account" | translate
}}</label>
<span
title="{{ providerAccount }}"
>{{ providerAccount }}</span
>
</div>
</div>
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.resource_name" | translate
}}</label>
<span
title="{{ resourceName }}"
>{{ resourceName }}</span
>
</div>
</div>
</div>
</div>
<div >
<div (click)="showMore = !showMore">
<label *ngIf="showMore">{{
"discovery_inventory.resource_overview.show_less" | translate
}}</label>
<label *ngIf="!showMore">{{
"discovery_inventory.resource_overview.show_more" | translate
}}</label>
</div>
</div>
</div>
</div>
<div >
<div >
<label >{{"discovery_inventory.resource_overview.configuration_details" | translate}}</label>
</div>
<div >
<div >
<div >
<div >
<label >{{"discovery_inventory.resource_overview.account_id" | translate}}</label>
<span
title="{{ providerAccount }}"
>{{ providerAccount }}</span
>
</div>
</div>
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.provider_account_name" | translate
}}</label>
<span
title="{{ providerAccount }}"
>{{ providerAccountName }}</span
>
</div>
</div>
</div>
<div >
<div >
<div >
<label >{{"discovery_inventory.resource_overview.creation_date" | translate}}</label>
<span
title="{{ creationDate }}"
>{{ creationDate }}</span
>
</div>
</div>
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.provider_lastupdated" | translate
}}</label>
<span
title="{{ providerLastUpdated }}"
>{{ providerLastUpdated }}</span
>
</div>
</div>
</div>
<div >
<div >
<div >
<label >{{
"discovery_inventory.resource_overview.arn" | translate
}}</label>
<span
title="{{ crn }}"
>{{ crn }}</span
>
</div>
</div>
<div >
<div >
<label >{{"discovery_inventory.resource_overview.provider_status" | translate}}</label>
<span
title="{{ providerStatus }}"
>{{ providerStatus }}</span
>
</div>
</div>
</div>
<div >
<div >
<div >
<label >{{"discovery_inventory.resource_overview.tags" | translate}}</label>
<div>
<span >
<table>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr *ngFor="let k of keyValue; let i = index;">
<td>{{k.Key}}</td>
<td>{{k.Value}}</td>
</tr>
</table>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
CodePudding user response:
2nd test
You are calling component.ngOnInit(); which internally calls getDetails();
3rd test
You are calling getDetails();
IMO, both are doing same, either you can delete your 2nd test and have your 3rd test calling component.ngOnInit(); with the mock api response or have the same mock API response in your 2nd test.
CodePudding user response:
The problem
The problem is that your test is constructing and providing an invalid date to the angular DatePipe
. Since the pipe lives in your HTML, you only see the error after running detectChanges
since the template is only rendered then.
The error happens in your lastUpdated | date:dateTimeFormat
part here
<label >{{ 'discovery_inventory.resource_overview.discovery_lastupdated' | translate }}
{{lastupdated | date : dateTimeFormat}} {{dateTimeZone}}</label>
Since your getResourceOverview
is executed in your test (due to an ID being set), the following line will also be executed:
// 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)) || '--' ;
This effectively assings an invalid date (constructed with new Date(undefined)
) to your lastupdated
instance variable, hence leading to the error you are seeing.
The solution
You either have to make sure that you are providing all the nescessary data in your test and/or make your code more robust. In this case I highly recommend checking for undefined/null correctly before providing data to your pipe:
<label >
{{ '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>
Additionally, you should make sure to assign undefined if you cannot construct a valid date to your variable:
this.lastupdated = result.raw_items[0].gpd.discovery_lastupdated
? new Date(Date.parse(result.raw_items[0].gpd.discovery_lastupdated))
: undefined ;
CodePudding user response:
describe('ResourceOverviewComponent', () => {
let component: ResourceOverviewComponent;
let fixture: ComponentFixture<ResourceOverviewComponent>;
let resourceOverviewService: any;
const data = {"raw_items":[{"uuid":"123456789:i-0105345f167219fbf:vms","gpd":{"version":"0.1","discovery_lastupdated":"2021-09-08T05:36:08Z","correlation_id":"aws-123456789-i-0105345f167219fbf","core_credential_id":["credid"],"provider_code":"aws","provider_account":"123456789","provider_account_name":"accountname","provider_account_details":{"cb84865d-3477-40f3-9131-4462273cfedb":"accountname"},"provider_resource_type":"vms","provider_resource_id":"i-0105345f167219fbf","provider_resource_name":"cd-642","provider_resource_lastupdated":"","provider_resource_created":"2021-08-30T06:12:49Z","provider_resource_crn":"arn:aws:ec2:us-east-1:123456789:vms/i-0105345f167219fbf","provider_resource_location":"us-east-1","provider_status":"running","location_geo_coordinates":{"latitude":38.86726,"longitude":-77.233959},"status":"Active","service_category":"compute","tags":[{"Key":"Name","Value":"cd-642"}],"raw_config_data":null,"extended_data":null}}],"meta":null,"pagination":{"count":1,"total":1,"offset":0,"limit":10,"first":"https://local-core.gravitant.net/discovery/api/inventory/v1/service?limit=10","last":"https://local-core.gravitant.net/discovery/api/inventory/v1/service?limit=10","next":"","prev":""}};;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ResourceOverviewComponent ],
imports: [
CarbonComponentsModule,CarbonModule,CbTranslationUiLibModule.forRoot(),
RouterModule.forRoot([]),
],
providers: [
{ provide: NGXLogger, useClass: MockNGXLogger },
{ provide: HttpWrapperService, useClass: mockHttpWrapperService },
{ provide: Router, useClass: class { navigate = jasmine.createSpy("navigate"); }},
ApiService,
ApiContractService,
ResourceOverviewService,
TranslationService,
CbCommonDiscoveryManagementMicroUiService,
{
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(ResourceOverviewComponent);
component = fixture.componentInstance;
resourceOverviewService = TestBed.inject(ResourceOverviewService);
spyOn(resourceOverviewService, "getResourceOverview").and.callFake(() => {
return of(data)
})
fixture.detectChanges();
});
it('should create component ', () => {
expect(component).toBeTruthy();
});
it('should init object for action tabs', async () => {
component.ngOnInit();
expect(component).toBeTruthy();
});
it('should get resource data ', () => {
component.getDetails();
expect(component).toBeTruthy();
});
});