fixture.detectchanges() method resulting in test case to fail
Asked Answered
G

2

0

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

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>
Garbage answered 8/11, 2021 at 5:52 Comment(4)
I don't think the issue is with fixture.detectChanges(), during the test case execution the DatePipe expecting a valid date and since you are not mocking any of you component instance properties, you are seeing that error, try mocking the local variables or have conditional operators to check null/undefined in your main method.Tempest
But what about the the third test case here. If i am writing fixture.detectChanges in beforeEach function then all test cases are failing. Though i am mocking an api call in my third test case. @TempestGarbage
Try mocking the API response in the second test with valid data, it should go through.Tempest
Hey I just did what you said. All test cases are passed now and coverage of the component is 98% Which is perfect. I am sharing test file below. Is this the correct way to write please let me know @TempestGarbage
G
2

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 class="currenttimestamp">{{ '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 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>

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 ;
Galactometer answered 8/11, 2021 at 7:13 Comment(1)
This solved my error. Thanks for the explanation @pascalpuetz. I have understood now.Garbage
T
2

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.

Tempest answered 8/11, 2021 at 6:53 Comment(1)
Yeah I figured that out. Thanks @Ramana.Garbage
G
2

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 class="currenttimestamp">{{ '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 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>

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 ;
Galactometer answered 8/11, 2021 at 7:13 Comment(1)
This solved my error. Thanks for the explanation @pascalpuetz. I have understood now.Garbage

© 2022 - 2024 — McMap. All rights reserved.