Angular 单元测试结合最新
Angular unit test combineLatest
我已经开始在 Angular 中使用 Jest Framework 进行单元测试了。但是,我陷入了需要对 combineLatest RxJS 运算符进行单元测试的情况。我的组件如下所示。
组件:
public userData;
public productData;
constructor(
private readonly userService: UserService,
private readonly productService: ProductService
) {}
public ngOnInit() {
this.initialize();
}
public initialize() {
combineLatest([
this.userService.getUserData(),
this.productService.getProductData()
])
.subscribe([userData, productData] => {
this.userData = userData;
this.productData = productData;
});
}
我已经模拟了我的服务和我的单元测试如下所示。
it('should initialize user and product data', fakeAsync(() => {
spyOn(userService, 'getUserData');
spyOn(productService, 'getProductData');
component.initialize();
tick();
fixture.detectChanges();
expect(userService.getUserData).toHaveBeenCalled();
expect(productService.getProductData).toHaveBeenCalled();
expect(component.userData).toBeDefined();
expect(component.productData).toBeDefined();
}));
此测试失败并显示 Received: undefined。 但是在使用单个 observable 时,同样类型的测试有效。
我会尝试用不同的方法回答这个问题。
让我们看看您的代码,测试简单的代码块比测试长代码更容易。
将变量分配给您的属性会容易得多。这样你就可以简单地测试 属性
见下面改进的代码;
constructor(
private readonly userService: UserService,
private readonly productService: ProductService
) {}
userData: any;
productData: any;
userData$ = this.userService.getUserData().pipe(
tap(userData => this.userData = userData)
);
productData$ = this.productService.getProductData().pipe(
tap(productData = productData => this.productData = productData)
)
v$ = combineLatest([this.userData$, this.productData$]).pipe(
map(([userData, productData]) => ({ userData, productData }))
)
initialize() {
v$.subscribe()
}
ngOnInit() {
this.initialize();
}
返回初始测试
现在让我们尝试测试您的初始代码并了解为什么测试不起作用。
来自官方documentation
When any observable emits a value, emit the last emitted value from each
让我们看看下面的代码
spyOn(userService, 'getUserData');
spyOn(productService, 'getProductData');
以上代码是否发出任何值?答案是否定的,因此代码 this.userService.getUserData()
和 this.productService.getProductData()
不会 return 任何值,因此不会发出任何值。 combineLatest
因此不会发出任何值。
解决方案
您将需要 return 来自 spyOn()
函数的 Observable
,例如
spyOn(userService, 'getUserData').and.returnValue(of({}));
现在 Observable 将发射并且测试将通过
关于更好代码的最终提案。
您可以考虑使用 async
管道来处理您的订阅。使用 async
管道,您的代码可以编写如下
constructor(
private readonly userService: UserService,
private readonly productService: ProductService
) {}
userData$ = this.userService.getUserData()
productData$ = this.productService.getProductData()
v$ = combineLatest([this.userData$, this.productData$]).pipe(
map(([userData, productData]) => ({ userData, productData }))
)
在你的html
<ng-container *ngIf='v$ | async'>
{{ v.userData | json }}
{{ v.productData| json }}
</ng-container>
在你的测试文件中你可以有
it('should initialize user and product data', fakeAsync(() => {
spyOn(userService, 'getUserData').and.returnValue(of({}));
spyOn(productService, 'getProductData').and.returnValue(of({}));
tick();
fixture.detectChanges();
component.v$.subscribe({
next: (v) => {
expect(v.userData).toBeDefined();
expect(v.productData).toBeDefined();
}
})
}));
上面的测试只是检查如果我们订阅 v$
,那么 userData
和 productData
被定义
我已经开始在 Angular 中使用 Jest Framework 进行单元测试了。但是,我陷入了需要对 combineLatest RxJS 运算符进行单元测试的情况。我的组件如下所示。
组件:
public userData;
public productData;
constructor(
private readonly userService: UserService,
private readonly productService: ProductService
) {}
public ngOnInit() {
this.initialize();
}
public initialize() {
combineLatest([
this.userService.getUserData(),
this.productService.getProductData()
])
.subscribe([userData, productData] => {
this.userData = userData;
this.productData = productData;
});
}
我已经模拟了我的服务和我的单元测试如下所示。
it('should initialize user and product data', fakeAsync(() => {
spyOn(userService, 'getUserData');
spyOn(productService, 'getProductData');
component.initialize();
tick();
fixture.detectChanges();
expect(userService.getUserData).toHaveBeenCalled();
expect(productService.getProductData).toHaveBeenCalled();
expect(component.userData).toBeDefined();
expect(component.productData).toBeDefined();
}));
此测试失败并显示 Received: undefined。 但是在使用单个 observable 时,同样类型的测试有效。
我会尝试用不同的方法回答这个问题。
让我们看看您的代码,测试简单的代码块比测试长代码更容易。
将变量分配给您的属性会容易得多。这样你就可以简单地测试 属性
见下面改进的代码;
constructor(
private readonly userService: UserService,
private readonly productService: ProductService
) {}
userData: any;
productData: any;
userData$ = this.userService.getUserData().pipe(
tap(userData => this.userData = userData)
);
productData$ = this.productService.getProductData().pipe(
tap(productData = productData => this.productData = productData)
)
v$ = combineLatest([this.userData$, this.productData$]).pipe(
map(([userData, productData]) => ({ userData, productData }))
)
initialize() {
v$.subscribe()
}
ngOnInit() {
this.initialize();
}
返回初始测试
现在让我们尝试测试您的初始代码并了解为什么测试不起作用。 来自官方documentation
When any observable emits a value, emit the last emitted value from each
让我们看看下面的代码
spyOn(userService, 'getUserData');
spyOn(productService, 'getProductData');
以上代码是否发出任何值?答案是否定的,因此代码 this.userService.getUserData()
和 this.productService.getProductData()
不会 return 任何值,因此不会发出任何值。 combineLatest
因此不会发出任何值。
解决方案
您将需要 return 来自 spyOn()
函数的 Observable
,例如
spyOn(userService, 'getUserData').and.returnValue(of({}));
现在 Observable 将发射并且测试将通过
关于更好代码的最终提案。
您可以考虑使用 async
管道来处理您的订阅。使用 async
管道,您的代码可以编写如下
constructor(
private readonly userService: UserService,
private readonly productService: ProductService
) {}
userData$ = this.userService.getUserData()
productData$ = this.productService.getProductData()
v$ = combineLatest([this.userData$, this.productData$]).pipe(
map(([userData, productData]) => ({ userData, productData }))
)
在你的html
<ng-container *ngIf='v$ | async'>
{{ v.userData | json }}
{{ v.productData| json }}
</ng-container>
在你的测试文件中你可以有
it('should initialize user and product data', fakeAsync(() => {
spyOn(userService, 'getUserData').and.returnValue(of({}));
spyOn(productService, 'getProductData').and.returnValue(of({}));
tick();
fixture.detectChanges();
component.v$.subscribe({
next: (v) => {
expect(v.userData).toBeDefined();
expect(v.productData).toBeDefined();
}
})
}));
上面的测试只是检查如果我们订阅 v$
,那么 userData
和 productData
被定义