组件初始化后,单元测试如下: ![image-20210412140340871](https://img.kancloud.cn/02/09/02094c24306181be7f64f30e2ca15c5a_1758x192.png) 错误信息提示说以`classId`命名的`FormControl`上没有找到`value accessor`。如果以前报此类错误,我们有些茫然是正常的。但学习过自定义FormControl后,排醒此错误便应该有些眉目了。只所以发生上述错误,是由于在V层中我们使用了如下代码: ```html <app-clazz-select formControlName="clazzId"></app-clazz-select> ``` 上述错误产生的根本原因是当前动态测试模块并不认识`app-clazz-select`,当然就更不知道`app-clazz-select`是我们自定义的`FormControl`了。 ## import 我们按下图尝试修正出现的单元测试的错误: ![image-20210412103338742](https://img.kancloud.cn/9d/f5/9df50ef0ec594819c419800866d1fd1b_2282x450.png) 在动态测试模块中引入班级选择模块: ```typescript +++ b/first-app/src/app/student/add/add.component.spec.ts @@ -2,6 +2,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {AddComponent} from './add.component'; import {ReactiveFormsModule} from '@angular/forms'; +import {ClazzSelectModule} from '../../clazz/clazz-select/clazz-select.module'; describe('student -> AddComponent', () => { let component: AddComponent; @@ -11,7 +12,8 @@ describe('student -> AddComponent', () => { await TestBed.configureTestingModule({ declarations: [AddComponent], imports: [ - ReactiveFormsModule + ReactiveFormsModule, + ClazzSelectModule ] }) ``` `import`包括了`ClazzSelectComponent`组件的`ClazzSelectModule`后,错误依旧:`app-clazz-select`仍然未被识别为`FormControl`。 一般这种情况是在创建自己定义组件时没有完全符合以下两种规范造成的: - 组件未实现`ControlValueAccessor`接口 - 组件未`provider` -> `NG_VALUE_ACCESSOR` 对于有单元测试的我们而言,上述两种情况都不大可能,因为在班级选择组件的单元测试中,我们在父组件中对其进行了测试: ```typescript first-app/src/app/clazz/clazz-select/clazz-select.component.spec.ts @Component({ template: ` <app-clazz-select [formControl]="clazzId"></app-clazz-select>` }) class TestComponent { clazzId = new FormControl(); } ``` 这说明`app-clazz-select`必然是符合`FormControl`规范的。 ## 私有与公有 要解决这个问题,就要谈谈模块中的组件、指令和管道的公有**与**私有**了。 默认情况下处于模块`declarations`声明中的组件、指令和管道都是**私有**的,在当前模块的任意组件中可以自由地使用它们。而当模块(A)被`import`进入其它模块(B)时,由于A模块中元素的**私有**性,B模块无法使用A模块中的任意私有元素(组件、指令和管道)。 所以在当前的代码下,进行`imports`时,实际如下图所示: ![image-20210412144236951](https://img.kancloud.cn/75/35/75350e239c5901dd595a0347897a843a_1390x414.png) 如上图所示:私有的班级选择组件并没有成功的并引入到动态测试模块中来,所以动态测试模块并无法解析`app-clazz-select`,当然就更别谈将其做为`FormControl`来看待了。 ## exports 若想将Angular中的组件、指令和管道设置为公有的,则需要将其添加到`exports`中: ```typescript +++ b/first-app/src/app/clazz/clazz-select/clazz-select.module.ts @@ -9,6 +9,9 @@ import {ReactiveFormsModule} from '@angular/forms'; imports: [ CommonModule, ReactiveFormsModule + ], + exports: [ + ClazzSelectComponent ] }) export class ClazzSelectModule { ``` 此时`ClazzSelectComponent`的属性变更为**公有**,当动态测试模块引入班级选择模块时便开始同步引入了这个班级选择组件了。当然也就认识了`app-clazz-select`,以及感知到其是一个合格的`FormControl`了。 ![image-20210412144647751](https://img.kancloud.cn/5b/26/5b261d301d17edd064d3b1d5a76007fd_1390x426.png) 此时不能识别为`FormControl`的错误消失,转而显示新的错误如下: ![image-20210412144938734](https://img.kancloud.cn/02/d5/02d5e34a8517002f5de6b00271708416_1586x162.png) 加入MockApi后错误消失: ```typescript +++ b/first-app/src/app/student/add/add.component.spec.ts @@ -3,6 +3,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {AddComponent} from './add.component'; import {ReactiveFormsModule} from '@angular/forms'; import {ClazzSelectModule} from '../../clazz/clazz-select/clazz-select.module'; +import {MockApiTestingModule} from '../../mock-api/mock-api-testing.module'; describe('student -> AddComponent', () => { let component: AddComponent; @@ -13,7 +14,8 @@ describe('student -> AddComponent', () => { declarations: [AddComponent], imports: [ ReactiveFormsModule, - ClazzSelectModule + ClazzSelectModule, + MockApiTestingModule ] }) .compileComponents(); ``` 最终效果如下: ![image-20210412145049101](https://img.kancloud.cn/11/5c/115c549d2861430b51e831bd9860313d_1164x742.png) 最后,手动的发送后面的模拟数据,并重新渲染V层,以使班级选择组件中默认有数据可选: ```typescript +++ b/first-app/src/app/student/add/add.component.spec.ts @@ -30,5 +30,7 @@ describe('student -> AddComponent', () => { fit('should create', () => { expect(component).toBeTruthy(); + getTestScheduler().flush(); + fixture.detectChanges(); }); }); ``` ![image-20210412145309498](https://img.kancloud.cn/4b/f6/4bf636333dc19886c487809d5a626097_1180x268.png) | 名称 | 链接 | | ---------------- | ------------------------------------------------------------ | | `imports` 数组 | [https://angular.cn/guide/bootstrapping#the-imports-array](https://angular.cn/guide/bootstrapping#the-imports-array) | | `exports` | [https://angular.cn/api/core/NgModule#exports](https://angular.cn/api/core/NgModule#exports) | | 共享特性模块 | [https://angular.cn/guide/sharing-ngmodules#sharing-modules](https://angular.cn/guide/sharing-ngmodules#sharing-modules) | | 本节源码(含答案) | [https://github.com/mengyunzhi/angular11-guild/archive/step7.2.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.2.1.zip) |