本小节进行StudentService.page方法的功能开发。打开src/app/service文件夹并找到student.service.ts及student.service.spec.ts。 # 功能代码 ``` import {HttpClient, HttpParams} from '@angular/common/http'; ... export class StudentService { ... /** * 分页 * @param params name:名称,sno:学号,klassId:班级ID,page:第几页,size:每页大小 */ page(params: { name?: string, sno?: string, klassId?: number, page?: number, size?: number}): Observable<{ totalPages: number, content: Array<Student> }> { const url = 'http://localhost:8080/Student'; /* 设置默认值① */ if (params.page === undefined) { params.page = 0; } if (params.size === undefined) { params.size = 10; } /* 初始化查询参数 */ const queryParams = new HttpParams()➊ .➋set('name', params.name ? params.name : '')➌ .set('sno', params.sno ? params.sno : ''②) .set('klassId', params.klassId ? params.klassId.toString() : '') .set('page', params.page.toString()) .set('size', params.size.toString()); console.log(queryParams);★ return this.httpClient.get<{ totalPages: number, content: Array<Student> ③}>(url, {params: queryParams})➍; } ``` * ① 设置默认值的一种方式 * ➊ 初始化参数类型为HttpParams * ➋ 使用连续操作来添加查询参数 * ➌ set(string, string),发起http查询时参数的类型必须为字符串(或字符串数组) * ② 设置默认值的另一种方式 * ★ 对某个类型还不熟悉的时候,将对象打印到控制台很关键 * ③ 定义get方法返回值的格式 * ➍ `HttpClient.get(请求地址, 其它选项)`,需要传入查询参数则使用`HttpClient.get(请求地址, {params: HttpParams})`。 # 测试代码 在功能代码第一次使用HttpClient的带有参数的get方法,在正式的进行mock测试之前,我们先来看看它会发起什么样的真实请求。此时,单元测试中引入的HttpClientTestingModule便不符合我们此时的需求了。我们将其暂时删除掉,引入会发起真实http请求的HttpClientModule。 ``` describe('service -> StudentService', () => { let service: StudentService; beforeEach(() => TestBed.configureTestingModule({ imports: [ HttpClientTestingModule, ✘ HttpClientModule, ✚ ] })); ``` 然后如下编写page单元测试,并在chrome中打开控制台。 ``` describe('service -> StudentService', () => { ... /* 分页测试 */ fit('page', () => { service.page({}).subscribe(); service.page({name: 'name'}).subscribe(); service.page({page: 2}).subscribe(); }); ``` 观察控制台,的确发起了我们预期的请求信息: ![](https://img.kancloud.cn/cf/18/cf1898d0de67f6a4a7df6acd34af4dce_890x421.png) 当传入空对象时,以默认值发起GET请求;当传入name值时,以正确的name值发起的GET请求;当传入page时,以正确的page值发起请求。 ## MockHttp 查看完发起的真正请求后,继续切换为模拟的Http请求模块来进行进一步测试: ``` describe('service -> StudentService', () => { let service: StudentService; beforeEach(() => TestBed.configureTestingModule({ imports: [ HttpClientTestingModule, ✚ HttpClientModule, ✘ ] })); ``` 测试的思路与save方法相同:①构造模拟返回值 ②以特定的参数值调用测试方法 ③断言发起了预期的http请求 ④断言接收到了预期的数据。 ``` /* 分页测试 */ fit('page', () => { /* 模拟返回数据 */ const mockResult = { totalPages: 10, content: new Array(new Student({}), new Student({})) }; /* 进行订阅,发送数据后将called置true */ let called = false; service.page({}).subscribe((success: { totalPages: number, content: Array<Student> }) => { called = true;② expect(success.totalPages).toEqual(10); expect(success.content.length).toBe(2); }); /* 断言发起了http请求,方法为get;请求参数值符合预期 */ const req = TestBed.get(HttpTestingController).expectOne((request: HttpRequest<any>) => { return request.url === 'http://localhost:8080/Student'; ➋ });➊ expect(req.request.method).toEqual('GET'); expect(req.request.params.get('name')).toEqual(''); ➌ expect(req.request.params.get('sno')).toEqual(''); expect(req.request.params.get('klassId')).toEqual(''); expect(req.request.params.get('page')).toEqual('0'); expect(req.request.params.get('size')).toEqual('10'); req.flush(mockResult); ① expect(called).toBe(true);③ }); ``` * ➊ expectOne(matchFn) matchFn为匹配函数,该函数被调用时传入值类型为HttpRequest。 * ➋ 返回值为true表示匹配成功 * ➌ 断言返回了默认的请求参数 * ①②③ 顺序执行,断言接收到了返回数据且返回数据符合预期 ## 断言参数 本着测试粒度最小化原则,再新建一个测试用例: ``` /* 分页参数测试 */ fit('page params test', () => { service.page({name: 'name', sno: 'sno', klassId: 1, page: 2, size: 20}).subscribe(); /* 断言发起了http请求,方法为get;请求参数值符合预期 */ const req = TestBed.get(HttpTestingController).expectOne( request => request.url === 'http://localhost:8080/Student' ); ➊ expect(req.request.method).toEqual('GET'); expect(req.request.params.get('name')).toEqual('name'); ① expect(req.request.params.get('sno')).toEqual('sno'); expect(req.request.params.get('klassId')).toEqual('1'); expect(req.request.params.get('page')).toEqual('2'); expect(req.request.params.get('size')).toEqual('20'); req.flush({}); ➋ }); ``` * ➊ 当箭头函数中仅有一行代码且使用return返回时,则可以简写为此形式 * ① 断言使用了使用的参数发起了http请求 * ➋ 由于本例的测试内容为**参数是否正确传入**,所以可以完全忽略请求返回值,直接将`{}`做为返回值即可 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.6.7](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.6.7) | - | | HttpClient GET | [https://www.angular.cn/api/common/http/HttpClient#get](https://www.angular.cn/api/common/http/HttpClient#get) | - |