我有一个简单的场景,其中(角度17.3)我试图测试输入属性更改时组件的反应。 我有一个从父组件传递的属性,它在子组件中作为 @Input 出现。类似这样的东西:
家长 ...
title = 'Hola!'
...
父级标记:
...
<my-child-component [input_title]="title" />
...
在孩子身上:
....
@Input({ required: true }) input_title: String = '';
..
...
ngOnChanges(changes: SimpleChanges): void {
const simpleChange = changes['input_title'];
if (simpleChange.previousValue && (simpleChange.currentValue !== simpleChange.previousValue)) {
this.init(); // I WANT to test this method
}
}
...
所以基本上,我想测试这个方法并触发更改。此外,我想测试标题的更改。我怎样才能从单元测试中做到这一点?
我有类似的东西:
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
let compiled: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
...
it('should change the title of the component', () => {
const playSpy = spyOn(component, 'init');
console.log(component?.input_title);
const newTitle= 'blablabla';
component.input_title= newTitle;
fixture.detectChanges();
compiled = fixture.nativeElement as HTMLElement;
console.log(compiled.querySelector('h1')?.textContent);
});
这不起作用:)
通过使用测试主机组件(这是我和 Angular 的建议策略),你可以完成这项工作;即使使用 OnPush。我对你的 init 方法可能做的事情采取了随意的做法 - 长话短说,你需要设置你的标题两次,因为这就是你编写更改的方式。
简单组件的Html
<div id="initStatus">initialized: {{initialized}}</div>
.ts 文件
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'my-child-component',
templateUrl: './simple.component.html',
styleUrl: './simple.component.css'
})
export class SimpleComponent implements OnChanges {
initialized = false;
@Input({ required: true }) input_title: String = '';
ngOnChanges(changes: SimpleChanges): void {
const simpleChange = changes['input_title'];
if (simpleChange.previousValue && (simpleChange.currentValue !== simpleChange.previousValue)) {
this.init(); // I WANT to test this method
}
}
private init(): void {
this.initialized = true;
}
}
规格文件
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
@Component(({
template: `<my-child-component [input_title]="title"></my-child-component>`,
}))
class TestHostComponent {
title = 'first title';
}
describe('SimpleComponent', () => {
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SimpleComponent, TestHostComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should init', () => {
const debugElement = fixture.debugElement.query(By.css('#initStatus'));
expect(debugElement.nativeElement.textContent).toBe('initialized: false');
component.title = 'second title'; // here is what you are missing
fixture.detectChanges();
expect(debugElement.nativeElement.textContent).toBe('initialized: true');
});
});
现在,我们来讨论 ngOnChanges。它会针对每次更改而运行,这就是为什么我完全避免它以帮助防止性能问题。 这是新的 ts 文件,它执行相同的操作,但带有 setter 和 OnPush
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'my-child-component',
templateUrl: './simple.component.html',
styleUrl: './simple.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SimpleComponent {
currentTitle: string
initialized = false;
@Input({ required: true }) set input_title(value: string) {
const changed = !!this.currentTitle;
this.currentTitle = value;
if (changed) {
this.init();
}
}
private init(): void {
this.initialized = true;
}
}
以这种方式写出来可以更明显地看出,只有在更改现有标题时才会调用 init 。
祝你好运,编码愉快!