如何在 Angular Jasmine 规范中测试一个主题和可观察的 return 值?
How to test a subject and observable return value in Angular Jasmine Spec?
在 Angular9 中,我有一个如下所示的加载器服务。
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
export interface LoaderState {
show: boolean;
}
@Injectable({
providedIn: 'root'
})
export class LoaderService {
private loaderSubject = new Subject<LoaderState>();
loaderState = this.loaderSubject.asObservable();
constructor() { }
show() {
this.loaderSubject.next({show: true} as LoaderState);
}
hide() {
this.loaderSubject.next({show: false} as LoaderState);
}
}
我想测试 show()
和 hide()
方法。为此,我编写了如下规范。
it('should show', (done) => {
spyOn(loaderService, 'show').and.callThrough();
loaderService.show();
expect(loaderService.show).toHaveBeenCalled();
loaderService.loaderState.subscribe((state) => {
expect(state).toBe({show: true});
done();
});
});
但是当我 运行 这个
Chrome 86.0.4240 (Windows 10.0.0) LoaderService should show FAILED
Error: Timeout - Async callback was not invoked within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
问这个问题之前查了很多资料。但似乎无法找到合适的解决方案。我是 Jasmin 单元测试的新手。感谢任何帮助。
编辑:发布完整的规范文件以供参考。
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoaderComponent } from './loader.component';
import { LoaderService } from './loader.service';
describe('LoaderService', () => {
let component: LoaderComponent;
let fixture: ComponentFixture<LoaderComponent>;
let loaderService: LoaderService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoaderComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
loaderService = TestBed.inject(LoaderService);
});
it('should be create', () => {
expect(loaderService).toBeTruthy();
});
it('should show', (done) => {
spyOn(loaderService, 'show').and.callThrough();
loaderService.show();
expect(loaderService.show).toHaveBeenCalled();
loaderService.loaderState.subscribe((state) => {
expect(state).toBe({show: true});
done();
});
});
});
问题是您在实际方法调用之后调用订阅。因此,当您调用 show();
时,没有任何内容订阅该事件,并且由于 done() 回调在其中......它从未被调用过。
it('should show', (done) => {
service.loaderState.subscribe((state) => { //subscribe first
expect(state).toEqual({show: true}); // change toEqual instead of toBe since you're comparing objects
done();
});
spyOn(service, 'show').and.callThrough();
service.show(); // invoke after
expect(service.show).toHaveBeenCalled();
});
这是我对此模式的解决方案:
服务Class
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class MyService {
private myServiceEvent = new Subject<any>();
constructor() { }
success(): Observable<any> {
return this.myServiceEvent.asObservable();
}
doSomething(someData: any): void {
// stuff
this.myServiceEvent.next(someData);
}
}
使用该服务的组件(可观察对象将由调用该服务上的 doSomething
方法的其他组件或服务触发)
import { Component, OnInit } from '@angular/core';
import { MyService } from ./my-service.ts
@Component({
selector: 'app-mycomponent',
templateUrl: './app-mycomponent.component.html',
styleUrls: ['./app-mycomponent.component.scss']
})
export class AppMyComponent implements OnInit {
private test = '';
constructor(
private myService: MyService,
) { }
ngOnInit() {
this.myService.success().subscribe(data => {
test = data;
});
}
}
使用服务的 Observable 响应测试组件的规范。请注意服务模拟的构造以及 Subject 变量如何用于触发 Observable。
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Observable, Subject } from 'rxjs';
import { AppMyComponent } from './app-my.component';
import { MyService } from ./my-service.ts
describe('AppMyComponent', () => {
let component: AppMyComponent;
let fixture: ComponentFixture<AppMyComponent>;
const myServiceSubject = new Subject<any>();
const myServiceMock = jasmine.createSpyObj('MyService', [], {
success: () => myServiceSubject.asObservable()
});
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [ CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [ AppMyComponent ],
providers: [
{ provide: MyService, useValue: myServiceMock }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should execute subscription to MyService success event', fakeAsync(() => {
myServiceSubject.next('somedata');
tick();
expect(componenet.test).toEqual('somedata');
}));
在 Angular9 中,我有一个如下所示的加载器服务。
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
export interface LoaderState {
show: boolean;
}
@Injectable({
providedIn: 'root'
})
export class LoaderService {
private loaderSubject = new Subject<LoaderState>();
loaderState = this.loaderSubject.asObservable();
constructor() { }
show() {
this.loaderSubject.next({show: true} as LoaderState);
}
hide() {
this.loaderSubject.next({show: false} as LoaderState);
}
}
我想测试 show()
和 hide()
方法。为此,我编写了如下规范。
it('should show', (done) => {
spyOn(loaderService, 'show').and.callThrough();
loaderService.show();
expect(loaderService.show).toHaveBeenCalled();
loaderService.loaderState.subscribe((state) => {
expect(state).toBe({show: true});
done();
});
});
但是当我 运行 这个
Chrome 86.0.4240 (Windows 10.0.0) LoaderService should show FAILED Error: Timeout - Async callback was not invoked within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
问这个问题之前查了很多资料。但似乎无法找到合适的解决方案。我是 Jasmin 单元测试的新手。感谢任何帮助。
编辑:发布完整的规范文件以供参考。
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoaderComponent } from './loader.component';
import { LoaderService } from './loader.service';
describe('LoaderService', () => {
let component: LoaderComponent;
let fixture: ComponentFixture<LoaderComponent>;
let loaderService: LoaderService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoaderComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
loaderService = TestBed.inject(LoaderService);
});
it('should be create', () => {
expect(loaderService).toBeTruthy();
});
it('should show', (done) => {
spyOn(loaderService, 'show').and.callThrough();
loaderService.show();
expect(loaderService.show).toHaveBeenCalled();
loaderService.loaderState.subscribe((state) => {
expect(state).toBe({show: true});
done();
});
});
});
问题是您在实际方法调用之后调用订阅。因此,当您调用 show();
时,没有任何内容订阅该事件,并且由于 done() 回调在其中......它从未被调用过。
it('should show', (done) => {
service.loaderState.subscribe((state) => { //subscribe first
expect(state).toEqual({show: true}); // change toEqual instead of toBe since you're comparing objects
done();
});
spyOn(service, 'show').and.callThrough();
service.show(); // invoke after
expect(service.show).toHaveBeenCalled();
});
这是我对此模式的解决方案:
服务Class
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class MyService {
private myServiceEvent = new Subject<any>();
constructor() { }
success(): Observable<any> {
return this.myServiceEvent.asObservable();
}
doSomething(someData: any): void {
// stuff
this.myServiceEvent.next(someData);
}
}
使用该服务的组件(可观察对象将由调用该服务上的 doSomething
方法的其他组件或服务触发)
import { Component, OnInit } from '@angular/core';
import { MyService } from ./my-service.ts
@Component({
selector: 'app-mycomponent',
templateUrl: './app-mycomponent.component.html',
styleUrls: ['./app-mycomponent.component.scss']
})
export class AppMyComponent implements OnInit {
private test = '';
constructor(
private myService: MyService,
) { }
ngOnInit() {
this.myService.success().subscribe(data => {
test = data;
});
}
}
使用服务的 Observable 响应测试组件的规范。请注意服务模拟的构造以及 Subject 变量如何用于触发 Observable。
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Observable, Subject } from 'rxjs';
import { AppMyComponent } from './app-my.component';
import { MyService } from ./my-service.ts
describe('AppMyComponent', () => {
let component: AppMyComponent;
let fixture: ComponentFixture<AppMyComponent>;
const myServiceSubject = new Subject<any>();
const myServiceMock = jasmine.createSpyObj('MyService', [], {
success: () => myServiceSubject.asObservable()
});
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [ CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
declarations: [ AppMyComponent ],
providers: [
{ provide: MyService, useValue: myServiceMock }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should execute subscription to MyService success event', fakeAsync(() => {
myServiceSubject.next('somedata');
tick();
expect(componenet.test).toEqual('somedata');
}));