Angular 11 使用 Jest 进行测试 - rxjs 计时器出现异步错误
Angular 11 Testing with Jest - Aync error with rxjs timer
我在编写测试时遇到以下错误:
“超时 - 异步回调未在 jest.setTimeout.Error 指定的 5000 毫秒超时内调用:超时 - 异步回调未在 jest.setTimeout 指定的 5000 毫秒超时内调用。”
这是测试:
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [DashboardTimerComponent, FormatTimePipe],
imports: [BrowserAnimationsModule, ReactiveFormsModule],
providers: [FormBuilder],
}).compileComponents();
}),
);
beforeEach(() => {
fixture = TestBed.createComponent(DashboardTimerComponent);
component = fixture.componentInstance;
});
it('should stop counter and emit event', fakeAsync(() => {
spyOn(component.stopped, 'emit');
component.stopRequested = true;
component.runningTimer = { timer: 19 };
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector('#stop');
button.click();
expect(component.timer).toBeNull();
expect(component.stopped.emit).toHaveBeenCalled();
}));
这是组件:
@Component({
selector: 'dashboard-timer',
templateUrl: './dashboard-timer.component.html',
providers: [DashboardTimerService],
animations: [fadeInAnimation],
})
export class DashboardTimerComponent {
@Input() projects: any;
@Input() runningTimer: any = null;
@Output() started = new EventEmitter();
@Output() stopped = new EventEmitter();
public form: FormGroup;
public timer: number = null;
public stopRequested: boolean = false;
public counter: Subscription;
private project: FormControl = new FormControl('');
private note: FormControl = new FormControl('');
constructor(
private dashboardTimerService: DashboardTimerService,
private fb: FormBuilder,
) {}
ngOnInit() {
// initialize form
this.form = this.fb.group({
project: this.project,
note: this.note,
});
if (this.runningTimer) {
this.timer = this.runningTimer.timer;
this.form.controls['project'].setValue(this.runningTimer.project || '');
this.form.controls['note'].setValue(this.runningTimer.note || '');
this.counter = this.dashboardTimerService
.getCounter()
.subscribe(() => this.timer++);
}
}
/**
* check if stop requested, stop counter, emit stop to parent component
*/
stop(): void {
if (this.stopRequested === false) {
this.stopRequested = true;
setTimeout(() => {
this.stopRequested = false;
}, 5000);
return;
}
this.stopRequested = false;
this.counter.unsubscribe();
this.stopped.emit();
this.timer = null;
}
}
错误似乎是由这项服务引起的:
import { Injectable } from '@angular/core';
import { timer } from 'rxjs';
@Injectable()
export class DashboardTimerService {
getCounter() {
return timer(0, 1000);
}
}
我想计时器仍然是 运行,即使我在组件中取消订阅它也是如此。
非常感谢任何解决此问题的想法!
谢谢!
查看您的 stackblitz,该组件实际上提供了服务,这意味着该组件创建了自己的服务实例,并且不使用您在 TestBed
提供程序中提供的模拟值。
所以我的第一个问题是,这个服务真的需要在组件本身上提供吗?
如果是,我看到两个选项:
由于您的数据服务使用 1000 的计时器,因此您实际上需要等待正好那个时间。为此,我会结合使用 fakeAsync
和 tick
。
it("should stop counter and emit event", fakeAsync(() => {
spyOn(component.stopped, "emit");
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector("#stop");
button.click();
tick(1000); // -> wait till you know that the promise will be resolved
fixture.detectChanges();
expect(component.timer).toBeNull();
expect(component.stopped.emit).toHaveBeenCalled();
}));
另一种选择是在创建组件后实际覆盖注入的服务,并使用它来覆盖服务实例。
it("should stop counter and emit event", fakeAsync(() => {
spyOn(component.stopped, "emit");
// override the actual injected service with your mock values here
(<any>TestBed.inject(DashboardTimerService)).getCounter = jest.fn().mockReturnValue(of(0))
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector("#stop");
button.click();
tick(); // i would still use fakeAsync and tick, since you are handling
// observables, which are still async even if they emit directly
fixture.detectChanges();
expect(component.timer).toBeNull();
expect(component.stopped.emit).toHaveBeenCalled();
}));
我在编写测试时遇到以下错误: “超时 - 异步回调未在 jest.setTimeout.Error 指定的 5000 毫秒超时内调用:超时 - 异步回调未在 jest.setTimeout 指定的 5000 毫秒超时内调用。”
这是测试:
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [DashboardTimerComponent, FormatTimePipe],
imports: [BrowserAnimationsModule, ReactiveFormsModule],
providers: [FormBuilder],
}).compileComponents();
}),
);
beforeEach(() => {
fixture = TestBed.createComponent(DashboardTimerComponent);
component = fixture.componentInstance;
});
it('should stop counter and emit event', fakeAsync(() => {
spyOn(component.stopped, 'emit');
component.stopRequested = true;
component.runningTimer = { timer: 19 };
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector('#stop');
button.click();
expect(component.timer).toBeNull();
expect(component.stopped.emit).toHaveBeenCalled();
}));
这是组件:
@Component({
selector: 'dashboard-timer',
templateUrl: './dashboard-timer.component.html',
providers: [DashboardTimerService],
animations: [fadeInAnimation],
})
export class DashboardTimerComponent {
@Input() projects: any;
@Input() runningTimer: any = null;
@Output() started = new EventEmitter();
@Output() stopped = new EventEmitter();
public form: FormGroup;
public timer: number = null;
public stopRequested: boolean = false;
public counter: Subscription;
private project: FormControl = new FormControl('');
private note: FormControl = new FormControl('');
constructor(
private dashboardTimerService: DashboardTimerService,
private fb: FormBuilder,
) {}
ngOnInit() {
// initialize form
this.form = this.fb.group({
project: this.project,
note: this.note,
});
if (this.runningTimer) {
this.timer = this.runningTimer.timer;
this.form.controls['project'].setValue(this.runningTimer.project || '');
this.form.controls['note'].setValue(this.runningTimer.note || '');
this.counter = this.dashboardTimerService
.getCounter()
.subscribe(() => this.timer++);
}
}
/**
* check if stop requested, stop counter, emit stop to parent component
*/
stop(): void {
if (this.stopRequested === false) {
this.stopRequested = true;
setTimeout(() => {
this.stopRequested = false;
}, 5000);
return;
}
this.stopRequested = false;
this.counter.unsubscribe();
this.stopped.emit();
this.timer = null;
}
}
错误似乎是由这项服务引起的:
import { Injectable } from '@angular/core';
import { timer } from 'rxjs';
@Injectable()
export class DashboardTimerService {
getCounter() {
return timer(0, 1000);
}
}
我想计时器仍然是 运行,即使我在组件中取消订阅它也是如此。
非常感谢任何解决此问题的想法!
谢谢!
查看您的 stackblitz,该组件实际上提供了服务,这意味着该组件创建了自己的服务实例,并且不使用您在 TestBed
提供程序中提供的模拟值。
所以我的第一个问题是,这个服务真的需要在组件本身上提供吗?
如果是,我看到两个选项:
由于您的数据服务使用 1000 的计时器,因此您实际上需要等待正好那个时间。为此,我会结合使用 fakeAsync
和 tick
。
it("should stop counter and emit event", fakeAsync(() => {
spyOn(component.stopped, "emit");
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector("#stop");
button.click();
tick(1000); // -> wait till you know that the promise will be resolved
fixture.detectChanges();
expect(component.timer).toBeNull();
expect(component.stopped.emit).toHaveBeenCalled();
}));
另一种选择是在创建组件后实际覆盖注入的服务,并使用它来覆盖服务实例。
it("should stop counter and emit event", fakeAsync(() => {
spyOn(component.stopped, "emit");
// override the actual injected service with your mock values here
(<any>TestBed.inject(DashboardTimerService)).getCounter = jest.fn().mockReturnValue(of(0))
fixture.detectChanges();
const button = fixture.debugElement.nativeElement.querySelector("#stop");
button.click();
tick(); // i would still use fakeAsync and tick, since you are handling
// observables, which are still async even if they emit directly
fixture.detectChanges();
expect(component.timer).toBeNull();
expect(component.stopped.emit).toHaveBeenCalled();
}));