当前组件中引用了TeacherSelect组件。在单元测试中使用了Test模块了具有同样selector的TeacherSelect组件。在班级管理与学生管理中,对嵌套组件的测试总是不尽如人意。以往的单元测试中,均未有效的测试出嵌套组件的`输入 @Input`及`输出 @Output`是否生效。之所以无法测试嵌入的组件,是由于没有获取测试嵌套组件的方法可供使用。无法获取嵌套组件,当然也就无法对其进行相关测试了。 ![](https://img.kancloud.cn/66/11/66112db43d844551e5a12cae2219c5d7_217x228.png) 不过在测试中却可以轻松的获取到被注入的服务。基于此,一种间接测试嵌套组件的方法应运而生: ![](https://img.kancloud.cn/65/d7/65d78d7f8eca5035901c8e90d1f941b8_273x271.png) 如此:预测试嵌套组件的@Input及@Output是否绑定成功,则直接获取服务并对其@Input、@Output测试即可。 ## 整理替身 首先使位于Test模块中的TeacherSelect组件替身拥有与原组件相同的输入与输出。 test/component/teacher-select/teacher-select.component.ts ```typescript export class TeacherSelectComponent implements OnInit { @Output() selected = new EventEmitter<Teacher>(); @Input() teacher: { id: number }; constructor() { } ``` ## 建立替身专用服务 接着在组件文件夹test/component/teacher-select/中建立此组件的专用服务`TeacherSelectService` ``` panjiedeMac-Pro:teacher-select panjie$ ng g s TeacherSelect --skip-tests CREATE src/app/test/component/teacher-select/teacher-select.service.ts (142 bytes) ``` test/component/teacher-select/teacher-select.service.ts ```typescript export class TeacherSelectService { constructor() { } } ``` 注意:此服务仅运行在测试环境中,不需要注入到root模块中,故删除`angular cli`自动生成的`@Injectable`相关代码。以防止在非测试环境中误注入此测试专用服务。 删除`@Injectable`后若想让其被自动注入则需要手动的将其声明在TestModule中。 test/test.module.ts ```typescript providers: [ {provide: TeacherService, useClass: TeacherStubService}, TeacherSelectService ➊ ] }) export class TestModule { } ``` ➊ 相当于:`{provide: TeacherSelectService, useClass: TeacherSelectService}` ## 于替身组件中注入专用服务 test/component/teacher-select/teacher-select.component.ts ```typescript export class TeacherSelectComponent implements OnInit { @Output() selected = new EventEmitter<Teacher>(); @Input() teacher: { id: number }; constructor(private teacherSelectService: TeacherSelectService) { teacherSelectService.selected = this.selected; ➊ teacherSelectService.teacher = this.teacher; ➋ } ``` * ➊ 将组件中的输出绑定到服务上 * ➋ 将组件中的输入绑定到服务上 ## 完善专用服务 以应在服务中增加相应字段,完成与C层的输入输出一一绑定。 test/component/teacher-select/teacher-select.service.ts ```typescript import {EventEmitter} from '@angular/core'; import {Teacher} from '../../../norm/entity/Teacher'; export class TeacherSelectService { selected: EventEmitter<Teacher>; teacher: { id: number }; constructor() { } } ``` ## 完成测试 准备工作完毕后,开始完成TeacherSelect嵌套组件的测试: src/app/course/add/add.component.spec.ts ```typescript fit('嵌入TeacherSelect组件测试', () => { // 获取组件替身的专用服务 const teacherSelectService: TeacherSelectService = TestBed.get(TeacherSelectService); const teacher = new Teacher(null, null, null); // 服务弹出teacher,断言组件接收到teacher teacherSelectService.selected.emit(teacher); expect(component.course.teacher).toBe(teacher); }); ``` 测试结果: ![](https://img.kancloud.cn/d8/e7/d8e770a58a3dbee05d55a0c2475c7cd7_1750x214.png) ## 修正功能 按单元测试代码来修正组件功能 ### V层 src/app/course/add/add.component.html ```html <app-teacher-select (selected)="course.teacher"></app-teacher-select> ✘ <app-teacher-select (selected)="onTeacherSelect($event)"></app-teacher-select> ``` ### C层 src/app/course/add/add.component.ts ```typescript ngOnInit() { this.formGroup = this.formBuilder.group({ name: ['', [Validators.minLength(2), Validators.required]] }); this.course = new Course(); ✚ } onTeacherSelect($event: Teacher) { ✚ this.course.teacher = $event; ✚ } ✚ ``` 单元测试通过,说明由组件测试专用服务中弹射出的值被Add组件成功接收了,嵌套组件测试成功。 ![](https://img.kancloud.cn/a3/4a/a34a61ed2d48dd120bb2d4dc0028de55_245x42.png) # 总结 此方案使用将专用服务注入到嵌套组件的方法,间接的测试了嵌套组件是否被成功的绑定了输入与输出。虽然在一定程度上解决了无法直接测试嵌套组件的问题,但笔者认为此方案扔显臃肿,应该是笔者还没有参透angular团队在相关方案的测试思想。如果你在学习实践的路上发现了更好的测试方案,请果断放弃此方案。 >[warning] 在TeacherSelect组件中加入专用服务后,将引发历史单元测试中的一个注入错误,请尝试自行修正。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.2](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.2) | - |