对组件进行测试
对组件绑定的测试
例如在一个组件中,一个组件中模版里有一个标题的是
<h2 class='title'>{{title}}</h2>
组件的类文件中对应绑定了一个类文件中的属性,
it('should render title in a h2 tag ', () => {
const fixtureEle = TestBed.createComponent(EnvNewComponent);
fixtureEle.detectChanges();
const compiled = fixtureEle.nativeElement;
expect(compiled.querySelector('.title').textContent).toContain(component.title);
expect(compiled.querySelector('h2').textContent).toContain(component.title);
});
大体思路:
1. TestBed创建这个组件
通过 TestBed.createComponent() 方法创建出了当前的组件
2. 执行数据绑定
通过调用 fixture.detectChanges() 来要求 TestBed 执行数据绑定,才能获取到相应是数据绑定。
3. 查找元素
compiled.querySelector()是用来查找页面中的元素,可以根据标签、class属性值、等。
4. 断言判断
expect(compiled.querySelector(‘h2’).textContent).toContain(component.title);
查找出来的文字内容和组件中的属性进行比较是否一致。
需要注意的是TestBed.createComponent() 方法不会绑定数据,只有调用了detectChanges()才会进行数据绑定
查询具有某个属性的一系列元素使用:
compiled.querySelectorAll()
自动检测变更
在测试文件中,经常需要调用 detectChanges() ,这样有时会出现遗漏或者书写繁琐的问题,现在可以让Angular 测试环境自动运行变更检测。在这里可以使用 ComponentFixtureAutoDetect 服务提供商来配置 TestBed 就可以做到这一点
TestBed.configureTestingModule({
imports: [
NgZorroAntdModule,
TableModule,
RouterTestingModule,
SearchTableModule,
HttpClientTestingModule,
TranslateModule.forRoot()],
declarations: [EnvNewComponent, BreadcrumbStubComponent],
providers: [
TranslateService,
{provide: ComponentFixtureAutoDetect, useValue: true}
],
schemas: [NO_ERRORS_SCHEMA]
})
ComponentFixtureAutoDetect 也是有一定局限性的,在测试环境中改变了原有需要绑定的值时,自动检测变更是检测不出来的,只有显性调用 detectChanges() 才会检测出变化。
即:
it('should render title in a h2 tag ', () => {
const fixtureEle = TestBed.createComponent(EnvNewComponent);
// fixtureEle.detectChanges();
const compiled = fixtureEle.nativeElement;
component.title = 'Test Demo';
expect(compiled.querySelector('.title').textContent).toContain('NotEnvsHost.notEnvsHost');
expect(compiled.querySelector('h2').textContent).toContain(component.title);
});
上述代码中 component.title = ‘Test Demo’; 这个变更,自动检测变更是检测不出来的。
带有外部文件的组件
通常大部分的组件都是将模版、样式文件分开放在不同的文件中的,就像这样:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHPdtD2u-1571041448341)(evernotecid://621D2FF6-4E72-4E02-9043-55F31F42819B/appyinxiangcom/22553815/ENResource/p210)]
这是在告诉 Angular 编译器在编译期间读取外部文件,在运行 ng test 命令的时候不会报错,因为运行测试之前是会先编译的,但是在非 cli 环境下进行测试的时候,那个对这个组件的测试可能就会出现报错的现象。
Error: This test module uses the component BannerComponent
which is using a "templateUrl" or "styleUrls", but they were never compiled.
Please call "TestBed.compileComponents" before your test.
此时就需要调用一次 compileComponents() 了,通过调用 compileComponents() 来进行编译。
需要注意的是:compileComponents() 是异步的,所以,一般情况下,要把组件的准备函数beforeEach() 分成两个,一个住同步的,一个是异步的。
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeaderComponent ],
})
.compileComponents(); // compile template and css
}));
beforeEach(() => {
window.localStorage.setItem('menus', JSON.stringify(menus));
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
router = fixture.debugElement.injector.get(Router);
translate = fixture.debugElement.injector.get(TranslateService);
nzI18nService = fixture.debugElement.injector.get(NzI18nService);
fixture.detectChanges();
linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub));
routerLinks = linkDes.map(de => de.injector.get(RouterLinkDirectiveStub));
});
建议在需要的时候 compileComponents() 时调用此方法,不需要 compileComponents() 的时候调用它也不会有其他影响。运行 ng test 时永远都不需要调用 compileComponents(),但 CLI 生成的组件测试文件还是会调用它。
带有异步服务的组件
在组件中,有些数据是来自远方服务器的。
使用间谍(Spy)进行测试
对于组件的测试,应该只关心服务的公共 API 是否实现了预计的逻辑。在测试过程中,测试不会向远方服务器发送请求,而是对方法的调用和逻辑的处理进行模拟仿真处理。
- 先声明间谍(Spy)
let loginGetVersionSpy: jasmine.Spy;
let loginTestData: {};
const loginService = jasmine.createSpyObj('LoginService', ['getLogin']);
其中 loginTestData 是模拟服务返回的数据对象,loginService 是模拟 LoginService 创建的一个关于 getLogin的Spy对象。
- 在TestBed.configureTestingModule中注入
TestBed.configureTestingModule({
imports: [
NgZorroAntdModule,
RouterTestingModule,
FormsModule,
HttpClientTestingModule,
],
providers: [
UtilService,
PublicDataService,
TranslateService,
{provide: NZ_ICONS, useValue: icons},
{provide: LoginService, useValue: loginService},
],
declarations: [LoginComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
- 使用间谍对象模拟数据返回
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
loginTestData = {
theme: {
configId: 11,
name: 'theme',
value: '0',
remark: '系统主题方案',
creatorId: null,
createTime: null,
modifierId: null,
modifyTime: null,
isdel: 0
}
};
loginGetVersionSpy = loginService.getLogin.and.returnValue(of(loginTestData));
});
在整个测试中,任何对 getLogin() 方法的调用都会收到一个包含测试模拟数据的可观察对象。 和真正的 getLogin() 方法不同,间谍跳过了服务器,直接返回了一个能立即解析出值的同步型可观察对象(Observable)。
带有输入输出参数的组件
对于有些组件,组件中的部分属性可能是来自于其他组件或者传输给其他组件的。如:
export class LoginComponent implements OnInit {
@Input() versionId;
@Output() buildConfDetailsBack = new EventEmitter<any>(); // 返回的信息
}
- 对于输入的参数,通过mock数据来模拟数据从其他组件传入
对于组件中 versionId:
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
const loginService = jasmine.createSpyObj('LoginService', ['getLogin']);
let translate: TranslateService;
let expectedVersionId: number;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NgZorroAntdModule,
RouterTestingModule,
FormsModule,
HttpClientTestingModule,
TranslateModule.forRoot(),
],
providers: [
UtilService,
PublicDataService,
TranslateService,
{provide: NZ_ICONS, useValue: icons},
],
declarations: [LoginComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
声明一个 number类型的 对外的versionId。
然后在每个beforeEach()中对组件的属性进行赋值
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
expectedVersionId = 43;
component.versionId = expectedVersionId;
fixture.detectChanges();
});
然后在进行测试。
it('should test outPut() back info', () => {
let buildConfDe: DebugElement;
let buildConfEl: HTMLElement;
let backInfo: {};
buildConfDe = fixture.debugElement.query(By.css('.buildConf'));
component.buildConfDetailsBack.subscribe((buildConfDetails: {}) => backInfo = buildConfDetails);
buildConfEl = buildConfDe.nativeElement;
buildConfEl.click();
expect(backInfo).toBe('match info');
});
通过fixture中获取 返回信息的点击元素,从中获取元素,并通过点击事件从触发EventEmitter。然后在进行对比。