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 ;