对 Angular 服务的单元测试,可以分为三类:
- 测试同步操作
- 测试异步操作
- 测试服务依赖
在测试服务前,我们先创建一个服务,实现对一个数组数据的检索和添加:
import { Injectable } from '@angular/core’;
import { Observable, of } from ‘rxjs’;
import { delay } from 'rxjs/operators’;
const books = ['《图书 壹》', '《图书 贰》', '《图书 叁》', '《图书 肆》', '《图书 伍》’];
@Injectable({
providedIn: ‘root’
})
export class AsyncService {
constructor() { }
getData(): Observable<string[]> {
return of(books).pipe(delay(500));
}
setData(name: string) {
return [...books, name ];
}
}
测试同步操作
测试服务的同步操作,是比较简单的,例如,测试一个返回简单数组数据的方法。
it('should set data', () => {
const result = service.setData('《图书 零》’);
expect(result.length).toBe(6);
});
在测试用例中,直接调用 setData
方法,并检查结果:如果集合的长度增加了 1,则表示测试通过。
测试异步操作
在对异步操作进行测试时,需要订阅 getData
方法,在方法执行完成后,再对结果进行检查验证。
it('should get data', (done: DoneFn) => {
service.getData().subscribe(books => {
expect(books.length).toBe(5);
done();
});
});
因为 Karma 不知道异步操作何时执行完成,需要使用 done
方法通知测试框架,表明异步操作已完成,可以进行结果验证。
测试服务依赖
在一个服务的业务逻辑中,经常会包含访问远程 API 的情况,那么该如何测试这样的服务呢?
在下面的这个服务中,包含了 getBooks
和 addBook
两个方法,均调用了远程的 API 服务,都需要依赖 HttpClient
完成网络请求。
import { Injectable } from '@angular/core’;
import { HttpClient } from "@angular/common/http”;
import { Observable } from ‘rxjs’;
@Injectable({
providedIn: ‘root’
})
export class DataService {
constructor(private http: HttpClient) { }
getBooks(): Observable<string[]> {
return this.http.get<string[]>('api/books’);
}
addBook(name: string) {
return this.http.post<string>('api/books', {book: name});
}
}
- 我们先来测试
getBooks
方法:
it('should get books', () => {
const books = ['图书 零', '图书 陆’];
service.getBooks().subscribe(books => expect(books.length).toBe(2));
const req = httpTestingController.expectOne('api/books’);
expect(req.request.method).toEqual('GET’);
req.flush(books);
});
HttpTestingController
类提供了 expectOne
方法,接受一个网络地址参数(URL)。expectOne
方法会创建一个模拟的请求对象,并确保请求只发一次。
- 再对
addBook
方法进行测试:
service.addBook('《球状闪电》').subscribe();
const req = httpTestingController.expectOne('api/books’);
expect(req.request.method).toEqual('POST’);
expect(req.request.body).toEqual({book: '《球状闪电》’})
req.flush(‘’);
});
因为添加操作使用的是 POST
方法,需要验证请求体包含了合适的数据。