TA的每日心情 | 郁闷 2022-8-29 14:43 |
---|
签到天数: 1 天 连续签到: 1 天 [LV.1]测试小兵
|
如何对 Service [url=]单元测试[/url] ?
使用 HttpTestingController 将大幅简化单元测试
Contents
1.Version
2.User Story
3.Task
4.Architecture
5.Implementation
1.PostService
2.AppComponent
6.Conclusion
7.Sample Code
8.Reference
凡与显示相关逻辑,我们会写在 component;凡与资料相关逻辑,我们会写在 service。而
service 最常见的应用,就是透过HttpClient?存取 API。
对于 service 单元测试而言,我们必须对?HttpClient?加以隔离;而对 component 单元测试
而言,我们必须对?service?加以隔离,我们该如何对 service 与 component 进行单元测试呢?
Version
Angular CLI 1.6.2
Node.js 8.9.4
Angular 5.2.2
User Story
●Header 会显示Welcome to app!
●程序一开始会显示所有 post
●按Add Post会呼叫POST API 新增 post
●按List Posts会呼叫GET API 回传 所有 post
Task
●目前有PostService使用HttpClient存取 API,为了对?PostService?做?单元测试,必须对
HttpClient加以隔离
●目前有AppComponent使用PostService, 为了对?AppComponent?做?单元测试,必须
对PostService加以隔离
Architecture
●AppComponent负责新增 post与显示 post?的界面显示;而PostService?负责 API 的串接
●根据依赖反转原则,AppComponent不应该直接相依于PostService,而是两者相依于
interface
●根据界面隔离原则,AppComponent只相依于它所需要的 interface,因此以
AppComponent的角度订出PostInterface,且PostService必须实作此 interface
●因为AppComponent与PostService都相依于PostInterface,两者都只知道PostInterface?
而已,而不知道彼此,因此AppComponent与PostService彻底解耦合
●透过 DI 将实作PostInterface的PostService注入到AppComponent,且将?HttpClient注
入到PostService
Implementation
AppComponent与?PostService的实作并非本文的重点,本文的重点在于实作?
AppComponent与PostService的单元测试部分。
PostService
post.service.spec.ts
- import { TestBed } from '@angular/core/testing';
- import { PostService } from './post.service';
- import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
- import { PostInterfaceToken } from '../interface/injection.token';
- import { Post } from '../../model/post.model';
- import { environment } from '../../environments/environment';
- import { PostInterface } from '../interface/post.interface';
- describe('PostService', () => {
- let postService: PostInterface;
- let mockHttpClient: HttpTestingController;
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [
- HttpClientTestingModule
- ],
- providers: [
- {provide: PostInterfaceToken, useClass: PostService}
- ]
- });
- postService = TestBed.get(PostInterfaceToken, PostService);
- mockHttpClient = TestBed.get(HttpTestingController);
- });
- it('should be created', () => {
- expect(PostService).toBeTruthy();
- });
- it(`should list all posts`, () => {
- /** act */
- const expected: Post[] = [
- {
- id: 1,
- title: 'Design Pattern',
- author: 'Dr. Eric Gamma'
- }
- ];
- postService.listPosts$().subscribe(posts => {
- /** assert */
- expect(posts).toEqual(expected);
- });
- /** arrange */
- const mockResponse: Post[] = [
- {
- id: 1,
- title: 'Design Pattern',
- author: 'Eric Gamma'
- }
- ];
- mockHttpClient.expectOne({
- url: `${environment.apiServer}/posts`,
- method: 'GET'
- }).flush(mockResponse);
- });
- it(`should add post`, () => {
- /** act */
- const expected: Post = {
- id: 1,
- title: 'OOP',
- author: 'Sam'
- };
- postService.addPost(expected).subscribe(post => {
- /** assert */
- expect(post).toBe(expected);
- });
- /** arrange */
- mockHttpClient.expectOne({
- url: `${environment.apiServer}/posts`,
- method: 'POST'
- }).flush(expected);
- });
- afterEach(() => {
- mockHttpClient.verify();
- });
- });
复制代码 14行
- TestBed.configureTestingModule({
- imports: [
- HttpClientTestingModule
- ],
- providers: [
- {provide: PostInterfaceToken, useClass: PostService}
- ]
- });
复制代码 Angular 有 module 观念,若使用到了其他 module,必须在imports设定;若使用到 DI,则必须在providers设定。
若只有一个 module,则在AppModule设定。
但是单元测试时,并没有使用AppModule的设定,因为我们可能在测试时使用其他替代 module,也可能自己实作 fake 另外 DI。
Angular 提供了TestBed.configureTestingModule(),让我们另外设定跑测试时的imports与providers部分。
15行
- imports: [
- HttpClientTestingModule
- ],
复制代码 原本HttpClient使用的是HttpClientModule,这会使得HttpClient真的透过网络去打 API,
这就不符合单元测试隔离的要求,因此 Angular 另外提供HttpClientTestingModule取代
HttpClientModule。
18 行
- providers: [
- {provide: PostInterfaceToken, useClass: PostService}
- ]
复制代码 由于我们要测试的就是PostService,因此PostService?也必须由 DI 帮我们建立。
但因爲?PostService?是基于PostInterface?建立,因此必须透过PostInterfaceToken?
mapping 到?PostService。
23 行
- postService = TestBed.get(PostInterfaceToken, PostService);
- mockHttpClient = TestBed.get(HttpTestingController);
复制代码 由providers设定好 interface 与 class 的 mapping 关系后,我们必须透过 DI 建立
postService与mockHttpClient。
其中HttpTestingController相当于 mock 版的?HttpClient,因此取名为mockHttpClient。
TestBed.get() 其实相当于new,只是这是藉由 DI 帮我们new?而已
|
|