Angular 中的单元测试:使用 Jasmine 模拟可观察到的 RxJS
Unit testing in Angular: Mocking RxJS observable with Jasmine
我正在对 Angular 12 组件进行单元测试。该组件在初始化时获取从服务返回的可观察值(参见下面的 thing.service.ts
)。它被分配给一个主题,该主题通过 async
管道显示在 html 模板中(参见下面的 app.component.html
)。
AppComponent.ts
export class AppComponent {
public errorObjectSubject = null;
public thingsSubject: Subject<Array<IThing>> = new Subject();
constructor(private readonly _service: ThingService) {
this._getAllProducts();
}
private async _getAllProducts(): Promise<void> {
this._service.getAllObservableAsync()
.pipe(take(1),
catchError(err => {
this.errorObjectSubject = err;
return throwError(err);
})
).subscribe(result => { this.thingsSubject.next(result) });
}
}
模板使用 async
管道订阅 public thingsSubject: Subject<Array<IThing>> = new Subject();
:
app.component.html
<div>
<app-thing *ngFor="let thing of thingsSubject | async" [thing]="thing"></app-thing>
</div>
thing.service.ts
constructor(private readonly _http: HttpClient) { }
public getAllObservableAsync(): Observable<Array<IThing>> {
return this._http.get<Array<IThing>>('https://jsonplaceholder.typicode.com/todos'); }
这是测试设置。
app.component.spec.ts
describe('AppComponent', () => {
let component: AppComponent,
fixture: ComponentFixture<AppComponent>,
dependencies: { thingService: ThingServiceStub };
function getThings(): Array<DebugElement> {
return fixture.debugElement.queryAll(By.directive(ThingComponentStub));
}
beforeEach(async () => {
dependencies = {
thingService: new ThingServiceStub()
};
await TestBed.configureTestingModule({
declarations: [AppComponent, ThingComponentStub],
providers: [
{ provide: ThingService, useValue: dependencies.thingService }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
//fixture.detectChanges();
});
describe('on initialisation', () => {
let getThingsSubject: Subject<Array<IThing>>;
beforeEach(() => {
getThingsSubject = new Subject();
(dependencies.thingService
.getAllObservableAsync as jasmine.Spy).and.returnValue(
getThingsSubject.asObservable()
);
fixture.detectChanges();
});
it('should fetch all of the things', () => {
//fixture.detectChanges();
expect(
dependencies.thingService.getAllObservableAsync
).toHaveBeenCalledWith();
});
describe('when the things have been fetched', () => {
beforeEach(fakeAsync(() => {
getThingsSubject.next()
// getThingsSubject.next([
// {
// userId: 1,
// id: 1,
// title: 'string',
// completed: 'string'
// }
// ]);
//getThingsSubject.pipe().subscribe()
tick();
fixture.detectChanges();
}));
it('should display the things', () => {
expect(getThings()[0].componentInstance.product).toEqual({
name: 'product',
number: '1'
});
});
});
});
});
thing.service.stub.ts
export class ProductServiceStub {
public getAllObservableAsync: jasmine.Spy = jasmine.createSpy('getAllObservableAsync');
}
我正在尝试测试模板填充内容后它的工作原理 (IThing[]
)。我有一个 passing 规范调用模拟 observable:
it('should fetch all of the things', () => {
expect(
dependencies.thingService.getAllObservableAsync
).toHaveBeenCalledWith();
});
然而,当我尝试测试模板时,我遇到了“错误:未捕获(承诺):TypeError:无法读取未定义的属性 'pipe'”:describe('when the things have been fetched'
。 =24=]
我不太确定问题出在哪里。这是我如何设置对主题的订阅吗?还是变化检测?
我认为您调用事物的顺序可能是个问题。
试试这个:
describe('AppComponent', () => {
let component: AppComponent,
fixture: ComponentFixture<AppComponent>,
dependencies: { thingService: ThingServiceStub };
function getThings(): Array<DebugElement> {
return fixture.debugElement.queryAll(By.directive(ThingComponentStub));
}
beforeEach(async () => {
dependencies = {
thingService: new ThingServiceStub()
};
await TestBed.configureTestingModule({
declarations: [AppComponent, ThingComponentStub],
providers: [
{ provide: ThingService, useValue: dependencies.thingService }
]
}).compileComponents();
});
beforeEach(() => {
// !! This (.createComponent) is when the constructor is called, so mock the observable
// before here and change the subject to a BehaviorSubject.
// Maybe subject will work as well.
let getThingsSubject = new BehaviorSubject([{ name: 'product', number: '1' }]);
(dependencies.thingService.getAllObservableAsync as jasmine.Spy).and.returnValue(
getThingsSubject.asObservable()
);
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
describe('on initialisation', () => {
let getThingsSubject: Subject<Array<IThing>>;
it('should fetch all of the things', () => {
expect(
dependencies.thingService.getAllObservableAsync
).toHaveBeenCalledWith();
});
describe('when the things have been fetched', () => {
// maybe tick and fakeAsync are not needed but `fixture.detectChanges` is
beforeEach(fakeAsync(() => {
tick();
fixture.detectChanges();
}));
it('should display the things', () => {
// !! Add this log for debugging
console.log(fixture.nativeElement);
expect(getThings()[0].componentInstance.product).toEqual({
name: 'product',
number: '1'
});
});
});
});
});
我正在对 Angular 12 组件进行单元测试。该组件在初始化时获取从服务返回的可观察值(参见下面的 thing.service.ts
)。它被分配给一个主题,该主题通过 async
管道显示在 html 模板中(参见下面的 app.component.html
)。
AppComponent.ts
export class AppComponent {
public errorObjectSubject = null;
public thingsSubject: Subject<Array<IThing>> = new Subject();
constructor(private readonly _service: ThingService) {
this._getAllProducts();
}
private async _getAllProducts(): Promise<void> {
this._service.getAllObservableAsync()
.pipe(take(1),
catchError(err => {
this.errorObjectSubject = err;
return throwError(err);
})
).subscribe(result => { this.thingsSubject.next(result) });
}
}
模板使用 async
管道订阅 public thingsSubject: Subject<Array<IThing>> = new Subject();
:
app.component.html
<div>
<app-thing *ngFor="let thing of thingsSubject | async" [thing]="thing"></app-thing>
</div>
thing.service.ts
constructor(private readonly _http: HttpClient) { }
public getAllObservableAsync(): Observable<Array<IThing>> {
return this._http.get<Array<IThing>>('https://jsonplaceholder.typicode.com/todos'); }
这是测试设置。
app.component.spec.ts
describe('AppComponent', () => {
let component: AppComponent,
fixture: ComponentFixture<AppComponent>,
dependencies: { thingService: ThingServiceStub };
function getThings(): Array<DebugElement> {
return fixture.debugElement.queryAll(By.directive(ThingComponentStub));
}
beforeEach(async () => {
dependencies = {
thingService: new ThingServiceStub()
};
await TestBed.configureTestingModule({
declarations: [AppComponent, ThingComponentStub],
providers: [
{ provide: ThingService, useValue: dependencies.thingService }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
//fixture.detectChanges();
});
describe('on initialisation', () => {
let getThingsSubject: Subject<Array<IThing>>;
beforeEach(() => {
getThingsSubject = new Subject();
(dependencies.thingService
.getAllObservableAsync as jasmine.Spy).and.returnValue(
getThingsSubject.asObservable()
);
fixture.detectChanges();
});
it('should fetch all of the things', () => {
//fixture.detectChanges();
expect(
dependencies.thingService.getAllObservableAsync
).toHaveBeenCalledWith();
});
describe('when the things have been fetched', () => {
beforeEach(fakeAsync(() => {
getThingsSubject.next()
// getThingsSubject.next([
// {
// userId: 1,
// id: 1,
// title: 'string',
// completed: 'string'
// }
// ]);
//getThingsSubject.pipe().subscribe()
tick();
fixture.detectChanges();
}));
it('should display the things', () => {
expect(getThings()[0].componentInstance.product).toEqual({
name: 'product',
number: '1'
});
});
});
});
});
thing.service.stub.ts
export class ProductServiceStub {
public getAllObservableAsync: jasmine.Spy = jasmine.createSpy('getAllObservableAsync');
}
我正在尝试测试模板填充内容后它的工作原理 (IThing[]
)。我有一个 passing 规范调用模拟 observable:
it('should fetch all of the things', () => {
expect(
dependencies.thingService.getAllObservableAsync
).toHaveBeenCalledWith();
});
然而,当我尝试测试模板时,我遇到了“错误:未捕获(承诺):TypeError:无法读取未定义的属性 'pipe'”:describe('when the things have been fetched'
。 =24=]
我不太确定问题出在哪里。这是我如何设置对主题的订阅吗?还是变化检测?
我认为您调用事物的顺序可能是个问题。
试试这个:
describe('AppComponent', () => {
let component: AppComponent,
fixture: ComponentFixture<AppComponent>,
dependencies: { thingService: ThingServiceStub };
function getThings(): Array<DebugElement> {
return fixture.debugElement.queryAll(By.directive(ThingComponentStub));
}
beforeEach(async () => {
dependencies = {
thingService: new ThingServiceStub()
};
await TestBed.configureTestingModule({
declarations: [AppComponent, ThingComponentStub],
providers: [
{ provide: ThingService, useValue: dependencies.thingService }
]
}).compileComponents();
});
beforeEach(() => {
// !! This (.createComponent) is when the constructor is called, so mock the observable
// before here and change the subject to a BehaviorSubject.
// Maybe subject will work as well.
let getThingsSubject = new BehaviorSubject([{ name: 'product', number: '1' }]);
(dependencies.thingService.getAllObservableAsync as jasmine.Spy).and.returnValue(
getThingsSubject.asObservable()
);
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
});
describe('on initialisation', () => {
let getThingsSubject: Subject<Array<IThing>>;
it('should fetch all of the things', () => {
expect(
dependencies.thingService.getAllObservableAsync
).toHaveBeenCalledWith();
});
describe('when the things have been fetched', () => {
// maybe tick and fakeAsync are not needed but `fixture.detectChanges` is
beforeEach(fakeAsync(() => {
tick();
fixture.detectChanges();
}));
it('should display the things', () => {
// !! Add this log for debugging
console.log(fixture.nativeElement);
expect(getThings()[0].componentInstance.product).toEqual({
name: 'product',
number: '1'
});
});
});
});
});