Angular 在测试组件中对主题调用 next() 时未调用 Jasmine 间谍
Angular Jasmine spy is not getting called when calling the next() on a subject in the test component
通常在执行期望已调用模拟服务调用时它会成功执行。我现在遇到以下情况,基本上所有代码都被触发,但对间谍的期望被调用没有通过。
我使用最新的 Karma (4.1.0) 和 Jasmine (3.4.0) 和 Angular 8.x 版本。
我有以下 testBed 配置和一个测试套件。
fdescribe('DetailComponent', () => {
let component: DetailComponent;
let fixture: ComponentFixture<DetailComponent>;
let mockedResolvedData: TimesheetResolved;
let mockedTimesheet: Timesheet;
let mockedPermissions;
let mockTimesheetService;
let mockNotificationService;
let mockPermissionsService;
let mockRouter;
let mockActivatedRoute: ActivatedRoute;
beforeEach(async(() => {
mockedTimesheet = mockTimesheet();
mockedPermissions = mockPermissions();
mockedResolvedData = { timesheet: mockedTimesheet, error: null };
mockTimesheetService = jasmine.createSpyObj([
'patchTimesheet',
'getTimesheet',
]);
mockNotificationService = jasmine.createSpyObj(['showNotification']);
mockAuthenticationService = jasmine.createSpyObj(['getRole']);
TestBed.configureTestingModule({
imports: [
// left out MaterialDesign imports
NoopAnimationsModule,
FormsModule,
],
declarations: [
DetailComponent,
// list of MockComponents
],
providers: [
{ provide: TimesheetService, useValue: mockTimesheetService },
{ provide: NotificationService, useValue: mockNotificationService },
{ provide: AuthenticationService, useValue: mockAuthenticationService },
{ provide: NgxPermissionsService, useValue: mockPermissionsService },
],
});
mockRouter = TestBed.get(Router);
mockActivatedRoute = TestBed.get(ActivatedRoute);
}));
describe('when the resolvedData is filled: happy-flow (regular behavior)', () => {
beforeEach(() => {
fixture = TestBed.createComponent(TimesheetDetailComponent);
mockTimesheetService.getTimesheet.and.returnValue(of(mockedRefreshedTimesheet));
mockPermissionsService.getPermissions.and.returnValue(mockedPermissions);
mockTimesheetService.patchTimesheet.and.returnValue(of(new HttpResponse<Object>()));
component = fixture.componentInstance;
});
fit('should call the patch if the value from the remarkSubject is changed', () => {
// Arrange
fixture.detectChanges();
// Act
component.timesheetRemarksSubject.next('new value');
// Assert
expect(mockTimesheetService.patchTimesheet).toHaveBeenCalled();
});
});
该组件具有以下代码:
// left out imports
@Component({
selector: 'detail-cmp',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss'],
})
export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
private readonly destroyed = new Subject();
timesheetRemarksSubject: Subject<string> = new Subject<string>();
timesheet: Timesheet;
constructor(
private readonly timesheetService: TimesheetService,
private readonly notificationService: NotificationService,
private readonly changeDetectorRef: ChangeDetectorRef,
) {}
ngOnInit(): void {;
this.route.data.subscribe(data => {
const resolvedData: TimesheetResolved = data['resolvedData'];
this.errorMessage = resolvedData.error;
this.onTimesheetReceived(resolvedData.timesheet);
});
}
onTimesheetReceived(timesheet: Timesheet): void {
this.timesheet = timesheet;
if (timesheet) {
// do something
}
}
ngAfterViewInit(): void {
if (this.timesheet) {
console.log('ngAfterViewInit!');
this.changeDetectorRef.detectChanges();
this.setupTimesheetRemarksSubjectSubscription();
}
}
private setupTimesheetRemarksSubjectSubscription(): void {
console.log('setupTimesheetRemarksSubjectSubscription');
this.timesheetRemarksSubject
.pipe(
takeUntil(this.destroyed),
debounceTime(500),
distinctUntilChanged(),
)
.subscribe(remark => {
console.log('succesfully remark object added');
console.log('value of the remark is: ', remark);
this.timesheet.remarks = remark;
this.patchTimesheetRemark();
});
}
ngOnDestroy(): void {
console.log('ngOnDestroy!');
this.destroyed.next();
this.destroyed.complete();
}
private patchTimesheetRemark(): void {
console.log('patching timesheet remarks!');
this.timesheetService.patchTimesheet(this.timesheet.id, this.timesheet.remarks).subscribe(
() => {
console.log('succesfully patched');
this.notificationService.showNotification(//custom message);
},
);
}
}
在制作开箱即用的自定义组件时,会调用与我的间谍具有相同类型依赖关系的准系统...这很奇怪,因为我的设置基本相同。 https://stackblitz.com/edit/angular-bv7oj2 <- 这是准系统项目。可能最好只是将其复制粘贴到准系统 angular CLI 项目和 运行 ng test
中。
底线是,我的 setup/configuration 中的某些内容有所不同,因为它在准系统中有效。间谍接到电话了。在我自己的测试中它没有。我收到以下控制台日志:
结合失败的测试显示:
我有点不知道为什么间谍没有接到电话。我从代码中遗漏了最不重要的东西,所以如果我遗漏了什么,请告诉我,我会提供。
提前致谢!
进行异步测试时,您需要确保测试等待,尤其是当您的测试包含 debounceTime
时。为此,您可以使用 fakeAsync
测试设置并调用 tick(500)
,其中 500 是您设置为去抖动时间的时间。
勾号告诉测试实际等待 debounceTime 完成,只有当它结束时,您的间谍才会被调用。
通常在执行期望已调用模拟服务调用时它会成功执行。我现在遇到以下情况,基本上所有代码都被触发,但对间谍的期望被调用没有通过。
我使用最新的 Karma (4.1.0) 和 Jasmine (3.4.0) 和 Angular 8.x 版本。
我有以下 testBed 配置和一个测试套件。
fdescribe('DetailComponent', () => {
let component: DetailComponent;
let fixture: ComponentFixture<DetailComponent>;
let mockedResolvedData: TimesheetResolved;
let mockedTimesheet: Timesheet;
let mockedPermissions;
let mockTimesheetService;
let mockNotificationService;
let mockPermissionsService;
let mockRouter;
let mockActivatedRoute: ActivatedRoute;
beforeEach(async(() => {
mockedTimesheet = mockTimesheet();
mockedPermissions = mockPermissions();
mockedResolvedData = { timesheet: mockedTimesheet, error: null };
mockTimesheetService = jasmine.createSpyObj([
'patchTimesheet',
'getTimesheet',
]);
mockNotificationService = jasmine.createSpyObj(['showNotification']);
mockAuthenticationService = jasmine.createSpyObj(['getRole']);
TestBed.configureTestingModule({
imports: [
// left out MaterialDesign imports
NoopAnimationsModule,
FormsModule,
],
declarations: [
DetailComponent,
// list of MockComponents
],
providers: [
{ provide: TimesheetService, useValue: mockTimesheetService },
{ provide: NotificationService, useValue: mockNotificationService },
{ provide: AuthenticationService, useValue: mockAuthenticationService },
{ provide: NgxPermissionsService, useValue: mockPermissionsService },
],
});
mockRouter = TestBed.get(Router);
mockActivatedRoute = TestBed.get(ActivatedRoute);
}));
describe('when the resolvedData is filled: happy-flow (regular behavior)', () => {
beforeEach(() => {
fixture = TestBed.createComponent(TimesheetDetailComponent);
mockTimesheetService.getTimesheet.and.returnValue(of(mockedRefreshedTimesheet));
mockPermissionsService.getPermissions.and.returnValue(mockedPermissions);
mockTimesheetService.patchTimesheet.and.returnValue(of(new HttpResponse<Object>()));
component = fixture.componentInstance;
});
fit('should call the patch if the value from the remarkSubject is changed', () => {
// Arrange
fixture.detectChanges();
// Act
component.timesheetRemarksSubject.next('new value');
// Assert
expect(mockTimesheetService.patchTimesheet).toHaveBeenCalled();
});
});
该组件具有以下代码:
// left out imports
@Component({
selector: 'detail-cmp',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss'],
})
export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
private readonly destroyed = new Subject();
timesheetRemarksSubject: Subject<string> = new Subject<string>();
timesheet: Timesheet;
constructor(
private readonly timesheetService: TimesheetService,
private readonly notificationService: NotificationService,
private readonly changeDetectorRef: ChangeDetectorRef,
) {}
ngOnInit(): void {;
this.route.data.subscribe(data => {
const resolvedData: TimesheetResolved = data['resolvedData'];
this.errorMessage = resolvedData.error;
this.onTimesheetReceived(resolvedData.timesheet);
});
}
onTimesheetReceived(timesheet: Timesheet): void {
this.timesheet = timesheet;
if (timesheet) {
// do something
}
}
ngAfterViewInit(): void {
if (this.timesheet) {
console.log('ngAfterViewInit!');
this.changeDetectorRef.detectChanges();
this.setupTimesheetRemarksSubjectSubscription();
}
}
private setupTimesheetRemarksSubjectSubscription(): void {
console.log('setupTimesheetRemarksSubjectSubscription');
this.timesheetRemarksSubject
.pipe(
takeUntil(this.destroyed),
debounceTime(500),
distinctUntilChanged(),
)
.subscribe(remark => {
console.log('succesfully remark object added');
console.log('value of the remark is: ', remark);
this.timesheet.remarks = remark;
this.patchTimesheetRemark();
});
}
ngOnDestroy(): void {
console.log('ngOnDestroy!');
this.destroyed.next();
this.destroyed.complete();
}
private patchTimesheetRemark(): void {
console.log('patching timesheet remarks!');
this.timesheetService.patchTimesheet(this.timesheet.id, this.timesheet.remarks).subscribe(
() => {
console.log('succesfully patched');
this.notificationService.showNotification(//custom message);
},
);
}
}
在制作开箱即用的自定义组件时,会调用与我的间谍具有相同类型依赖关系的准系统...这很奇怪,因为我的设置基本相同。 https://stackblitz.com/edit/angular-bv7oj2 <- 这是准系统项目。可能最好只是将其复制粘贴到准系统 angular CLI 项目和 运行 ng test
中。
底线是,我的 setup/configuration 中的某些内容有所不同,因为它在准系统中有效。间谍接到电话了。在我自己的测试中它没有。我收到以下控制台日志:
结合失败的测试显示:
我有点不知道为什么间谍没有接到电话。我从代码中遗漏了最不重要的东西,所以如果我遗漏了什么,请告诉我,我会提供。
提前致谢!
进行异步测试时,您需要确保测试等待,尤其是当您的测试包含 debounceTime
时。为此,您可以使用 fakeAsync
测试设置并调用 tick(500)
,其中 500 是您设置为去抖动时间的时间。
勾号告诉测试实际等待 debounceTime 完成,只有当它结束时,您的间谍才会被调用。