问题描述
嗨,我有一个 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 在订阅之前正在解决。