鉴于以下代码,我尝试测试 Angular2 的
ngOnChanges
生命周期钩子:
import {
it,
inject,
fdescribe,
beforeEachProviders,
} from '@angular/core/testing';
import {TestComponentBuilder} from '@angular/compiler/testing';
import {Component, OnChanges, Input} from '@angular/core';
@Component({
selector: 'test',
template: `<p>{{value}}</p>`,
})
export class TestComponent implements OnChanges {
@Input() value: string;
ngOnChanges(changes: {}): any {
// should be called
}
}
fdescribe('TestComponent', () => {
let tcb: TestComponentBuilder;
beforeEachProviders(() => [
TestComponentBuilder,
TestComponent,
]);
beforeEach(inject([TestComponentBuilder], _tcb => {
tcb = _tcb;
}));
it('should call ngOnChanges', done => {
tcb.createAsync(TestComponent).then(fixture => {
let testComponent: TestComponent = fixture.componentInstance;
spyOn(testComponent, 'ngOnChanges').and.callThrough();
testComponent.value = 'Test';
fixture.detectChanges();
expect(testComponent.ngOnChanges).toHaveBeenCalled();
done();
}).catch(e => done.fail(e));
});
});
不幸的是,测试失败并显示消息
Expected spy ngOnChanges to have been called.
我知道我可以在本示例中检查 HTML 元素的内容,但我有一些代码需要在 ngOnChanes 生命周期挂钩内进行测试,所以这不是解决方案为我。我也不想在测试中直接调用testComponent.ngOnChanges({someMockData});
。
如何在测试中设置
TestComponent.value
以便调用 ngOnChanges
?
我想我参加聚会有点晚了,但这可能对将来的某些人有用。
自 Angular RC 5 发布以来,测试发生了一些变化。然而,这里的主要问题是当以编程方式设置输入时,不会调用
ngOnChanges
。 请参阅此了解更多信息。基本上,当输入仅通过视图传递时,会触发 OnChanges
钩子。
解决方案是让主机组件成为测试组件的父组件,并通过主机组件的模板传递输入。
这是完整的工作代码:
import {Component, OnChanges, Input, ViewChild} from '@angular/core';
import { TestBed } from '@angular/core/testing';
@Component({
selector: 'test',
template: `<p>{{value}}</p>`,
})
export class TestComponent implements OnChanges {
@Input() value: string;
ngOnChanges(changes: {}): any {
// should be called
}
}
/* In the host component's template we will pass the inputs to the actual
* component to test, that is TestComponent in this case
*/
@Component({
selector : `test-host-component`,
template :
`<div><test [value]="valueFromHost"></test></div>`
})
export class TestHostComponent {
@ViewChild(TestComponent) /* using viewChild we get access to the TestComponent which is a child of TestHostComponent */
public testComponent: any;
public valueFromHost: string; /* this is the variable which is passed as input to the TestComponent */
}
describe('TestComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({declarations: [TestComponent,TestHostComponent]}); /* We declare both the components as part of the testing module */
});
it('should call ngOnChanges', ()=> {
const fixture = TestBed.createComponent(TestHostComponent);
const hostComponent = fixture.componentInstance;
hostComponent.valueFromHost = 'Test';
const component = hostComponent.testComponent;
spyOn(component, 'ngOnChanges').and.callThrough();
fixture.detectChanges();
expect(component.ngOnChanges).toHaveBeenCalled();
})
});
ngOnChanges
钩子并将所需的更改对象传递到那里。但这并没有设置组件属性,仅调用更改逻辑。
const previousValue = moment('2016-03-01T01:00:00Z');
const currentValue = moment('2016-02-28T01:00:00Z');
const changesObj: SimpleChanges = {
prop1: new SimpleChange(previousValue, currentValue),
};
component.ngOnChanges(changesObj);
请注意,这种方法可以很好地测试
ngOnChanges
内部的逻辑,但它不会测试
@Input
装饰器是否已正确设置。
import {OnChanges, SimpleChange, SimpleChanges} from '@angular/core';
export function updateComponentInputs<T extends OnChanges>(
component: T,
changes: Partial<T>
) {
const simpleChanges: SimpleChanges = {};
Object.keys(changes).forEach(changeKey => {
component[changeKey] = changes[changeKey];
simpleChanges[changeKey] = new SimpleChange(
null,
changes[changeKey],
false
);
});
component.ngOnChanges(simpleChanges);
}
你可以像这样使用它:
testComponent.updateComponentInputs({value: 'test'});
这会将 testComponent.value 设置为“test”,并使用适当的更改事件调用 ngOnChanges。
setInput()
。这样,Angular 就会使用正确的参数调用 ngOnChanges:
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(() => {
// configure TestBed, etc.
// ...
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
});
it('should call ngOnChanges', () => {
spyOn(component, 'ngOnChanges').and.callThrough();
fixture.componentRef.setInput('value', 'Test');
fixture.detectChanges();
expect(component.ngOnChanges).toHaveBeenCalled();
});
});