由于未调用 ngOnit 语句,角度测试失败

问题描述

嗨,我有一个 angular 2 组件。我有一个测试。我的测试失败,因为我收到以下错误。以下是我在运行 ng test 时收到的错误

        Expected spy create to have been called with 
    [ <jasmine.objectContaining(Object({ name: 'test',campaign: Object({ id: '123' }) }))> ]
 but actual calls were [ Object({ name: 'test',campaign: undefined }) ].

以下是我的测试。

describe('CreateFlightComponent',() => {
  let component: CreateFlightComponent;
  let fixture: ComponentFixture<CreateFlightComponent>;
  let router: Router;

  let getCampaignSpy: jasmine.Spy;
  let createFlightSpy: jasmine.Spy;
  let navigateSpy: jasmine.Spy;
  let toastSpy: jasmine.Spy;
  let getUserSpy: jasmine.Spy;
  let getApprovalPeriodDateSpy: jasmine.Spy;
  let getTagOnsspy: jasmine.Spy;

  beforeEach(async(() => {
    Testbed.configureTestingModule({
      declarations: [CreateFlightComponent],providers: [
        FlightsService,CampaignsService,ToastService,AuthenticationService,SettingsService,{
          provide: ActivatedRoute,useValue: {
            paramMap: of(convertToParamMap({ id: '123' }))
          }
        },{ provide: ConfigService,useClass: ConfigServiceMock }
      ],imports: [
        TranslateModule.forRoot(),RouterTestingModule,HttpClientTestingModule
      ],schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents();
  }));

  beforeEach(() => {
    const campaignsService = Testbed.get(CampaignsService);
    getCampaignSpy = spyOn(campaignsService,'get').and.returnValue(
      of({
        id: '123'
      })
    );

    const flightsService = Testbed.get(FlightsService);
    createFlightSpy = spyOn(flightsService,'create').and.returnValue(
      of({ id: 'f321' })
    );
    getTagOnsspy = spyOn(flightsService,'getTagOns').and.returnValue(of([]));
    spyOn(flightsService,'getTagOnTargetRecipients').and.returnValue(
      of([] as TagOnTargetRecipient[])
    );

    const toastService = Testbed.get(ToastService);
    toastSpy = spyOn(toastService,'toast');

    const authenticationService = Testbed.get(AuthenticationService);
    getUserSpy = spyOn(authenticationService,'getUser').and.returnValue(
      of({
        account: { features: [{ code: FeatureCode.PROFILES }] } as Account
      } as User)
    );

    const settingsService = Testbed.get(SettingsService);
    getApprovalPeriodDateSpy = spyOn(
      settingsService,'getApprovalPeriodDate'
    ).and.returnValue(of(moment(new Date()).add(7,'days')));

    router = Testbed.get(Router);
    navigateSpy = spyOn(router,'navigate');
  });

  beforeEach(() => {
    fixture = Testbed.createComponent(CreateFlightComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create',() => {
    expect(component).toBeTruthy();
  });

  describe('handleSave',() => {

    it('should send a request to create a flight',fakeAsync( () => {
      
      fixture.detectChanges();
      tick();
      component.handleSave({ currentValue: { name: 'test' },stepIndex: 1 });
  
      expect(createFlightSpy).toHaveBeenCalledWith(
        jasmine.objectContaining({
          name: 'test',campaign: { id: '123' }
        })
      );
    }))

});


});

以下是我的组件类 CreateFlightComponent

export class CreateFlightComponent implements OnInit {
  campaign: Campaign;
  isLoading = true;
  isSaving = false;
  hasProfileFeature = false;
  hasLocationFeature = false;
  hasRecipientGatheringFeature = false;
  hasParameterisedContactListFeature = false;
  hasInteractiveSMSFeature = false;
  account: Account;
  inventories: Inventory[] = [];
  tagOns: TagOn[] = [];
  tagOnTargetRecipients: TagOnTargetRecipient[] = [];
  approvalPeriodStartDate: Moment;
  parameterisedContactList: ParameterisedContactList;
  addressprofile: Addressprofile;

  constructor(
    private router: Router,private route: ActivatedRoute,private translateService: TranslateService,private flightsService: FlightsService,private campaignsService: CampaignsService,private toastService: ToastService,private authenticationService: AuthenticationService,private settingsService: SettingsService
  ) {}

  ngOnInit() {
    this.route.paramMap
      .pipe(
        tap(() => (this.isLoading = true)),switchMap(paramMap =>
          combineLatest(
            this.authenticationService.getUser(),this.campaignsService.get(paramMap.get('id')),this.settingsService.getApprovalPeriodDate(),this.flightsService.getTagOns(),this.flightsService.getTagOnTargetRecipients(),this.flightsService.getInventories()
          )
        )
      )
      .subscribe(
        ([user,campaign,date,tagOns,tagOnTargetRecipients,allInventories]) => {
          this.isLoading = false;

          if (user.account) {
            this.account = user.account;
            this.inventories = this.account.inventories;

            if (this.account.features) {
              this.hasProfileFeature = this.account.features.some(
                feature => feature.code === FeatureCode.PROFILES
              );

              this.hasLocationFeature = this.account.features.some(
                feature => feature.code === FeatureCode.LOCATIONS
              );

              this.hasRecipientGatheringFeature = this.account.features.some(
                feature => feature.code === FeatureCode.RECIPIENT_GATHERING
              );

              this.hasParameterisedContactListFeature = this.account.features.some(
                feature =>
                  feature.code === FeatureCode.ParaMETERISED_CONTACT_LIST
              );

              this.hasInteractiveSMSFeature = this.account.features.some(
                feature =>
                  feature.code === FeatureCode.INteraCTIVE_SMS
              );

              this.addInteractiveSMS(this.hasInteractiveSMSFeature,allInventories,this.inventories )
            }

            if (this.account.addressprofile) {
              this.addressprofile = this.account.addressprofile;
            }
          }

          this.tagOns = tagOns;
          this.tagOnTargetRecipients = tagOnTargetRecipients;
          this.campaign = campaign;
          console.log(JSON.stringify(this.campaign));
          this.approvalPeriodStartDate = date;
        },() => {
          this.isLoading = false;
        }
      );
  }

  addInteractiveSMS( hasInteractiveSMSFeature:boolean,allInventories: Inventory[],inventories: Inventory[]) {
    let interactiveSMS =  allInventories.find(inventory => inventory.code === InventoryCode.INteraCTIVE_SMS);
    if(hasInteractiveSMSFeature && interactiveSMS){
       inventories.push(interactiveSMS);
    }
  }

  handleSave({
    currentValue,stepIndex
  }: {
    currentValue: Partial<Flight>;
    stepIndex: number;
  }) {
    const request: Partial<Flight> = {
      ...currentValue,campaign: this.campaign
    };
    const titleKey = 'headingS.FLIGHT_CREATED';
    let bodyKey = '';

    this.isSaving = true;

    this.flightsService
      .create(request)
      .pipe(
        switchMap(flight => {
          this.router.navigate(['/flights',flight.id,'edit'],{
            queryParams: { startStepIndex: stepIndex }
          });

          request.impressionLimit !== flight.impressionLimit
            ? (bodyKey = 'MESSAGES.IMPRESSIONS_CHANGED')
            : (bodyKey = 'MESSAGES.FLIGHT_SAVED_DRAFT');

          return request.impressionLimit !== flight.impressionLimit
            ? this.translateService.get([titleKey,bodyKey],{
                name: request.name,impressions: flight.impressionLimit
              })
            : this.translateService.get([titleKey,{
                name: request.name
              });
        })
      )
      .subscribe(
        translations => {
          this.isSaving = false;

          this.toastService.toast({
            type: 'success',title: translations[titleKey],body: translations[bodyKey],icon: 'paper-plane',timeout: 10000
          });
        },() => {
          this.isSaving = false;
        }
      );
  }

  saveAsDraft(currentValue: Partial<Flight>) {
    const titleKey = 'headingS.FLIGHT_CREATED';
    const bodyKey = 'MESSAGES.FLIGHT_SAVED_DRAFT';

    const request: Partial<Flight> = {
      ...currentValue,campaign: this.campaign,parameterisedContactList: this.parameterisedContactList,status: {
        code: FlightStatusCode.DRAFT
      }
    };

    this.isSaving = true;
    this.flightsService
      .create(request)
      .pipe(
        switchMap(flight => {
          this.router.navigate(['/flights',{
            queryParams: { startStepIndex: 1 }
          });

          return this.translateService.get([titleKey,{
            name: request.name
          });
        })
      )
      .subscribe(
        translations => {
          this.isSaving = false;
          this.toastService.toast({
            type: 'success',() => {
          this.isSaving = false;
        }
      );
  }

  handleUploadFormSubmit(value: ParameterisedContactList) {
    this.parameterisedContactList = value;
  }

  handleCancel() {
    this.router.navigate(['/campaigns',this.campaign.id]);
  }
}

我的测试是调用handleSave。我试图调试代码。我在 ngOnInit() 中的这个声明 this.campaign = campaign 中的 subscribe 方法中放置了一个调试点。但是它没有在那里进行调试。如有帮助,不胜感激

谢谢

请注意:

已将以下代码更新为以下内容,但出现错误

CreateFlightComponent should send a request to create a flight
[object ErrorEvent] thrown
TypeError: Cannot read property 'next' of undefined



  describe('CreateFlightComponent',() => {
      let component: CreateFlightComponent;
      let fixture: ComponentFixture<CreateFlightComponent>;
      let router: Router;
    
      let getCampaignSpy: jasmine.Spy;
      let createFlightSpy: jasmine.Spy;
      let navigateSpy: jasmine.Spy;
      let toastSpy: jasmine.Spy;
      let getUserSpy: jasmine.Spy;
      let getApprovalPeriodDateSpy: jasmine.Spy;
      let getTagOnsspy: jasmine.Spy;
      let myActivatedRouteObserver;
      
      beforeEach(() => {
    
        const  myActivatedRouteObservable = new Observable((observer) => {
          myActivatedRouteObserver = observer;
         });
    
      });
    
      beforeEach(async(() => {
    
        Testbed.configureTestingModule({
          declarations: [CreateFlightComponent],providers: [
            FlightsService,{
              provide: ActivatedRoute,useValue: myActivatedRouteObserver
            },useClass: ConfigServiceMock }
          ],imports: [
            TranslateModule.forRoot(),HttpClientTestingModule
          ],schemas: [NO_ERRORS_SCHEMA]
        }).compileComponents();
      }));
    
      beforeEach(() => {
        const campaignsService = Testbed.get(CampaignsService);
        getCampaignSpy = spyOn(campaignsService,'get').and.returnValue(
          of({
            id: '123'
          })
        );
    
        const flightsService = Testbed.get(FlightsService);
        createFlightSpy = spyOn(flightsService,'create').and.returnValue(
          of({ id: 'f321' })
        );
        getTagOnsspy = spyOn(flightsService,'getTagOns').and.returnValue(of([]));
        spyOn(flightsService,'getTagOnTargetRecipients').and.returnValue(
          of([] as TagOnTargetRecipient[])
        );
    
        const toastService = Testbed.get(ToastService);
        toastSpy = spyOn(toastService,'toast');
    
        const authenticationService = Testbed.get(AuthenticationService);
        getUserSpy = spyOn(authenticationService,'getUser').and.returnValue(
          of({
            account: { features: [{ code: FeatureCode.PROFILES }] } as Account
          } as User)
        );
    
        const settingsService = Testbed.get(SettingsService);
        getApprovalPeriodDateSpy = spyOn(
          settingsService,'getApprovalPeriodDate'
        ).and.returnValue(of(moment(new Date()).add(7,'days')));
    
        router = Testbed.get(Router);
        navigateSpy = spyOn(router,'navigate');
      });
    
      beforeEach(() => {
        fixture = Testbed.createComponent(CreateFlightComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create',() => {
        expect(component).toBeTruthy();
      });
    
    
    
        it('should send a request to create a flight',fakeAsync( () => {
          
    
          fixture.detectChanges();
    
          myActivatedRouteObserver.next(convertToParamMap({ id: '123' }));
    
          component.handleSave({ currentValue: { name: 'test' },stepIndex: 1 });
      
          expect(createFlightSpy).toHaveBeenCalledWith(
            jasmine.objectContaining({
              name: 'test',campaign: { id: '123' }
            })
          );
        }));
    
    });

解决方法

要从测试中运行 ngOnInit(),您必须调用 fixture.detectChanges()

我在您的代码中看到了这一点,因此 ngOnInit() 应该正在运行。但是,您的 ngOnInit() 代码订阅了一个 Observable,this.route.paramMap。我不清楚您在测试中在哪里解析 Observable 以便您的管道和订阅可以运行。

当您设置 ActivatedRoute 提供程序时,我会创建您可以完全控制的自己的 Observable。

创建 Observable:

let myActivatedRouteObserver;
constant myActivatedRouteObservable = new Observable((observer) => {
 myActivatedRouteObserver = observer;
}

为 ActivatedRoute 创建提供者:

        {
          provide: ActivatedRoute,useValue: {
            paramMap: myActivatedRouteObservable
          }
        },

然后当你想解析参数映射时:

myActivatedRouteObserver.next(convertToParamMap({ id: '123' }));

您必须在检测到更改后运行它,因为在此之前未订阅 observable。我怀疑这是您问题的根源,Observable 在订阅之前正在解决。