在 Angular 2 中对可观察对象进行单元测试
Unit testing an observable in Angular 2
在 Angular 2 中对返回 Observable 结果的服务进行单元测试的正确方法是什么?假设我们在 CarService 服务中有一个 getCars 方法 class:
...
export class CarService{
...
getCars():Observable<any>{
return this.http.get("http://someurl/cars").map( res => res.json() );
}
...
}
如果我尝试按以下方式编写测试,我会收到警告:'SPEC HAS NO EXPECTATIONS':
it('retrieves all the cars', inject( [CarService], ( carService ) => {
carService.getCars().subscribe( result => {
expect(result.length).toBeGreaterThan(0);
} );
}) );
使用 injectAsync 没有帮助,因为据我所知它适用于 Promise
个对象。
AsyncTestCompleter
已弃用 https://github.com/angular/angular/issues/5443。 injectAsync
替换了它
https://github.com/angular/angular/issues/4715#issuecomment-149288405
但 injectAsync
现在也已弃用
injectAsync
不再被弃用 https://github.com/angular/angular/pull/5721(另请参阅@ErdincGuzel 的评论)
it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
var c = PromiseWrapper.completer();
carService.getCars().subscribe( result => {
expect(result.length).toBeGreaterThan(0);
c.resolve();
} );
return c.promise;
}) );
最后我以一个工作示例结束。 Observable
class 有一个 toPromise 方法,可以将 Observable 对象转换为 Promise 对象。正确的方法应该是:
it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
return carService.getCars().toPromise().then( (result) => {
expect(result.length).toBeGreaterThan(0);
} );
}) );
但是虽然上面的代码适用于任何 Observable 对象,但我仍然遇到从 Http 请求返回的 Observable
s 的问题,这可能是一个错误。这是一个展示上述案例的 plunker:http://plnkr.co/edit/ak2qZH685QzTN6RoK71H?p=preview
更新:
从 beta.14 版本开始,它似乎可以与提供的解决方案一起正常工作。
Angular
(版本 2+)的正确方法:
it('retrieves all the cars', waitForAsync(inject([CarService], (carService) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
}));
异步 Observable 与同步 Observable
了解 Observable 可以是 同步或异步。
很重要
在您的特定示例中,Observable 是 异步的(它包装了一个 http 调用)。
因此,您必须使用 waitForAsync
函数在特殊的 异步测试区 中执行其体内的代码。它拦截并跟踪在其主体中创建的所有承诺,从而可以在异步操作完成后期待测试结果。
但是,如果您的 Observable 是 同步,例如:
...
export class CarService{
...
getCars():Observable<any>{
return Observable.of(['car1', 'car2']);
}
...
你不需要 waitForAsync
函数,你的测试会变得简单
it('retrieves all the cars', inject([CarService], (carService) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
});
弹珠
在一般测试 Observables 和特别是 Angular 时要考虑的另一件事是 marble testing.
您的示例非常简单,但通常逻辑比仅调用 http
服务更复杂,测试此逻辑变得令人头疼。
Marbles 使测试变得非常简短、简单和全面(它对测试特别有用ngrx effects)。
如果您使用 Jasmine
,您可以使用 jasmine-marbles, for Jest
there is jest-marbles, but if you prefer something else, there is rxjs-marbles,它应该与任何测试框架兼容。
Here 是使用弹珠重现和修复竞争条件的一个很好的例子。
https://angular.io/guide/testing目前展示了几种方式。这是一个:
it('#getObservableValue should return value from observable',
(done: DoneFn) => {
service.getObservableValue().subscribe(value => {
expect(value).toBe('observable value');
done();
});
});
我推荐这种方法,我认为它更优雅:
expectAsync(carService.getCars().toPromise()).toBeResolvedWith(myExpectedValue);
您还可以使用以下方法提供自己的异步匹配器:Jasmine Matcher
我设法让它工作的方法是在预期之后订阅和调用完成。
it('should equal bar', (done: any) => {
bar.getFoo().subscribe(v => {
expect(v).toBe('foo');
done();
});
});
在 Angular 2 中对返回 Observable 结果的服务进行单元测试的正确方法是什么?假设我们在 CarService 服务中有一个 getCars 方法 class:
...
export class CarService{
...
getCars():Observable<any>{
return this.http.get("http://someurl/cars").map( res => res.json() );
}
...
}
如果我尝试按以下方式编写测试,我会收到警告:'SPEC HAS NO EXPECTATIONS':
it('retrieves all the cars', inject( [CarService], ( carService ) => {
carService.getCars().subscribe( result => {
expect(result.length).toBeGreaterThan(0);
} );
}) );
使用 injectAsync 没有帮助,因为据我所知它适用于 Promise
个对象。
AsyncTestCompleter
已弃用 https://github.com/angular/angular/issues/5443。 injectAsync
替换了它
https://github.com/angular/angular/issues/4715#issuecomment-149288405
但 injectAsync
现在也已弃用
injectAsync
不再被弃用 https://github.com/angular/angular/pull/5721(另请参阅@ErdincGuzel 的评论)
it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
var c = PromiseWrapper.completer();
carService.getCars().subscribe( result => {
expect(result.length).toBeGreaterThan(0);
c.resolve();
} );
return c.promise;
}) );
最后我以一个工作示例结束。 Observable
class 有一个 toPromise 方法,可以将 Observable 对象转换为 Promise 对象。正确的方法应该是:
it('retrieves all the cars', injectAsync( [CarService], ( carService ) => {
return carService.getCars().toPromise().then( (result) => {
expect(result.length).toBeGreaterThan(0);
} );
}) );
但是虽然上面的代码适用于任何 Observable 对象,但我仍然遇到从 Http 请求返回的 Observable
s 的问题,这可能是一个错误。这是一个展示上述案例的 plunker:http://plnkr.co/edit/ak2qZH685QzTN6RoK71H?p=preview
更新:
从 beta.14 版本开始,它似乎可以与提供的解决方案一起正常工作。
Angular
(版本 2+)的正确方法:
it('retrieves all the cars', waitForAsync(inject([CarService], (carService) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
}));
异步 Observable 与同步 Observable
了解 Observable 可以是 同步或异步。
很重要在您的特定示例中,Observable 是 异步的(它包装了一个 http 调用)。
因此,您必须使用 waitForAsync
函数在特殊的 异步测试区 中执行其体内的代码。它拦截并跟踪在其主体中创建的所有承诺,从而可以在异步操作完成后期待测试结果。
但是,如果您的 Observable 是 同步,例如:
...
export class CarService{
...
getCars():Observable<any>{
return Observable.of(['car1', 'car2']);
}
...
你不需要 waitForAsync
函数,你的测试会变得简单
it('retrieves all the cars', inject([CarService], (carService) => {
carService.getCars().subscribe(result => expect(result.length).toBeGreaterThan(0));
});
弹珠
在一般测试 Observables 和特别是 Angular 时要考虑的另一件事是 marble testing.
您的示例非常简单,但通常逻辑比仅调用 http
服务更复杂,测试此逻辑变得令人头疼。
Marbles 使测试变得非常简短、简单和全面(它对测试特别有用ngrx effects)。
如果您使用 Jasmine
,您可以使用 jasmine-marbles, for Jest
there is jest-marbles, but if you prefer something else, there is rxjs-marbles,它应该与任何测试框架兼容。
Here 是使用弹珠重现和修复竞争条件的一个很好的例子。
https://angular.io/guide/testing目前展示了几种方式。这是一个:
it('#getObservableValue should return value from observable', (done: DoneFn) => { service.getObservableValue().subscribe(value => { expect(value).toBe('observable value'); done(); }); });
我推荐这种方法,我认为它更优雅:
expectAsync(carService.getCars().toPromise()).toBeResolvedWith(myExpectedValue);
您还可以使用以下方法提供自己的异步匹配器:Jasmine Matcher
我设法让它工作的方法是在预期之后订阅和调用完成。
it('should equal bar', (done: any) => {
bar.getFoo().subscribe(v => {
expect(v).toBe('foo');
done();
});
});