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

Angular test failing because of ngOnit statements not called

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

        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' }
        })
      );
    }))

});


});

下面是我的组件classCreateFlightComponent

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, bodyKey], {
                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', flight.id, 'edit'], {
            queryParams: { startStepIndex: 1 }
          });

          return this.translateService.get([titleKey, bodyKey], {
            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;
        }
      );
  }

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

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

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

谢谢

请注意:

已更新为以下代码,但出现错误。

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,
            CampaignsService,
            ToastService,
            AuthenticationService,
            SettingsService,
            {
              provide: ActivatedRoute,
              useValue: myActivatedRouteObserver
            },
            { 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();
      });
    
    
    
        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() 应该是 运行ning。但是,您的 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 在订阅之前正在解析。