使用angular中的单元测试,脱离后台独立开发
在前后台开发的项目中,我们当前已经做到了后台依靠单元测试,完全的脱离前台进行开发。那么,在进行单台开发时,是否也可以做到只依赖于UML
图,不依赖于后台进行独立的开发呢?答案是肯定的。
本文旨在带领你:在前台完全脱离后台开发路上更近一步。
前期代码准备
在此,我们以近期开发的Alice学生管理系统为例,将学院管理的index
组件拿出来,以展示脱离后台的前台。
文件列表:
CollegeIndexComponent: 学院管理INDEX列表组件 College: 学院实体 CollegeService: 学院服务 其它辅助文件略
CollegeIndexComponent 学院管理INDEX列表组件
import {Component, OnInit} from '@angular/core'; import {CollegeService} from '../../../../../service/college.service'; import {College} from '../../../../../entity/college'; import {Page} from '../../../../../entity/page'; @Component({ selector: 'app-college-index', templateUrl: './college-index.component.html', styleUrls: ['./college-index.component.css'] }) export class CollegeIndexComponent implements OnInit { page: number; size: number; total: number; collegeList: Array<College>; constructor(private collegeService: CollegeService) { } ngOnInit() { console.log(this.collegeService); this.collegeList = new Array<College>(); this.page = 1; this.size = 5; this.queryPage(); } queryPage() { this.collegeService.getCollegeByPage(this.page - 1, this.size) .subscribe((data: Page<College>) => { this.collegeList = data.content; this.total = data.totalElements; }, () => { console.log('network error'); }); } }
College: 学院实体
import {Teacher} from './teacher'; import {Major} from './major'; /** * 学院实体 */ export class College { id: number; // id name: string; // 名称 teacherList: Teacher[]; // 负责教师 majorList: Major[]; [propName: string]: any; /** * 获取学院实体 */ static instance(): College { const college = new College(); college.id = 1; college.name = '测试学院'; college.teacherList = new Array<Teacher>( Teacher.instance() ); college.majorList = new Array<Major>(); return college; } }
CollegeService: 学院服务
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { College } from '../entity/college'; import { Observable } from 'rxjs'; import { Page } from '../entity/page'; @Injectable({ providedIn: 'root', }) export class CollegeService { baseUrl = 'College'; constructor(protected http: HttpClient) {} /** *获取分页数据 */ public getCollegeByPage(page: number, size: number): Observable<Page<College>> { const params = { page: page.toString(), size: size.toString(), }; return this.http.get<Page<College>>(this.baseUrl + '/page', { params: params, }); } }
单元测试
代码如下:
import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {CollegeIndexComponent} from './college-index.component'; import {NzGridModule, NzDividerModule, NzTableModule, NzFormModule} from 'ng-zorro-antd'; import {HttpClientModule} from '@angular/common/http'; import {RouterTestingModule} from '@angular/router/testing'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; /** * 对测试的描述:CollegeIndexComponent */ describe('CollegeIndexComponent', () => { // 定义两个变量 let component: CollegeIndexComponent; let fixture: ComponentFixture<CollegeIndexComponent>; beforeEach(async(() => { // TestBed工作台 // TestBed.configureTestingModule 配置要测试的模块 // 这贴近于现实生活。现实生活中,我们测试一块电表是否正确. // 1. 需要一个专门用于测试的工作台 // 2. 需要对这个测试的工作进行配置,以让其符合测试电表的基础工作 TestBed.configureTestingModule({ // 声明要上工作台的组件 declarations: [CollegeIndexComponent], // 配置测试的依赖,没有这些模块,测试就进行不了。 // 比如我们测试电表是否准确,需要有交流电,需要有电流表,需要有电压表等 imports: [NzGridModule, HttpClientModule, NzDividerModule, NzTableModule, RouterTestingModule, ReactiveFormsModule, FormsModule, NzFormModule] }).compileComponents(); // 配置完后,开始编译要测试组件 })); // 每次测试前,均执行一遍 beforeEach(() => { // fix = 固定。用工作台创建一个供测试的组件。 // 由于其想要被测试,就必然还需要其它的一些脚手架。 // 我们把脚手架与被测试的组件组成的联合体称为:ComponentFixture 被固定的组件 fixture = TestBed.createComponent(CollegeIndexComponent); // 实例化要测试的组件 component = fixture.componentInstance; // 检测变化 fixture.detectChanges(); }); /** * 测试方法的描述信息:should create */ it('should create', () => { // 期待 组件 被成功创建 expect(component).toBeTruthy(); }); });
执行此单元测试,虽然能够通过,但将在控制台得到以下错误信息。
同时,展示的界面如下:
我们看到,在测试时依赖于httpClient
发起的请求,由于我们没有启动后台服务(即使启动了,使用绝对地址访问的方法也有问题),所以无法由后台获取数据。其实,即使是我们进行跨域的地址设置,由于后台我们往往会进行权限验证(是否登录,当前登录用户是否有当前操作的权限等),所以:想发起一个正常的请求,并不容易。
下面,让我们在不破坏原有服务层的基础上,自定义返回数据,从而规避http
请求,达到不需要后台,便可以看到真实数据的目的。
自定义返回数据
在单元测试时,调用beforeEach
及it
方法时均可以将@angular/core/testing -> injetct()
传入。在此,我们以beforeEach
为例,注入CollegeService
,并在测试组件实例化以前,对其getCollegeByPage
方法的返回值,进行重写。
/** * 每次测试前,均执行一遍 * inject([], () => {}) 单元测试中的方法 * CollegeService 要注入的服务 * StudentService 要注入的服务 (仅用于展示注入多个服务) * s为服务起的别名,类型为CollegeService,其对应注入的第一个参数 * t为服务起的别名,类型为StudentService,其对应注入的第二个参数(仅用于展示注入多个服务) */ beforeEach(inject([CollegeService, StudentService], (s: CollegeService, t: StudentService) => { console.log(s); console.log(t); // fix = 固定。用工作台创建一个供测试的组件。 // 由于其想要被测试,就必然还需要其它的一些脚手架。 // 我们把脚手架与被测试的组件组成的联合体称为:ComponentFixture 被固定的组件 fixture = TestBed.createComponent(CollegeIndexComponent); // 实例化要测试的组件 component = fixture.componentInstance; // 检测变化 fixture.detectChanges(); }));
使用如下方法改写返回值:
/** * 每次测试前,均执行一遍 * inject([], () => {}) 单元测试中的方法 * CollegeService 要注入的服务 * StudentService 要注入的服务(仅用于展示注入多个服务) * collegeService为服务起的别名,类型为CollegeService,其对应注入的第一个参数 * studentService为服务起的别名,类型为StudentService,其对应注入的第二个参数(仅用于展示注入多个服务) */ beforeEach(inject([CollegeService, StudentService], (collegeService: CollegeService, studentService: StudentService) => { // 变量初始化 const collegePage = new Page<College>(); collegePage.content = new Array<College>( College.instance() ); // 改写collegeService中的getCollegeByPage方法的返回值。 spyOn(collegeService, 'getCollegeByPage').and.returnValue(of(collegePage)); // fix = 固定。用工作台创建一个供测试的组件。 // 由于其想要被测试,就必然还需要其它的一些脚手架。 // 我们把脚手架与被测试的组件组成的联合体称为:ComponentFixture 被固定的组件 fixture = TestBed.createComponent(CollegeIndexComponent); // 实例化要测试的组件 component = fixture.componentInstance; // 检测变化 fixture.detectChanges(); }));
此时,我们再次测试,正常的通过了测试,控制台没有报错,而且:我们在浏览器上看到了我们的组件。最关键的是:我们即没有使用后台,也没有改变CollegeService
中的代码。
参考文献:
深度好文