[英]Programmatic changes to Angular 2 component does not execute ngOnChanges in unit test
I have a rather simple, working Angular 2 component. 我有一个相当简单的Angular 2组件。 The component renders some div sibling elements based off of an array of values that I have sorted.
该组件根据我已排序的值数组来呈现一些div兄弟元素。 This component implements
OnChanges
. 该组件实现
OnChanges
。 The sorting functionality happens during the execution of ngOnChanges()
. 排序功能在
ngOnChanges()
执行期间发生。 Initially, my @Input()
attribute references an array of values that are unsorted. 最初,我的
@Input()
属性引用未排序的值数组。 Once the change detection kicks in, the resulting DOM elements are sorted as expected. 一旦开始进行更改检测,就将按预期对生成的DOM元素进行排序。
I have written a Karma unit test to verify that the sorting logic in the component has taken place and that the expected DOM elements are rendered in sorted order. 我编写了一个Karma单元测试,以验证组件中的排序逻辑是否已发生以及预期的DOM元素是否按排序顺序呈现。 I programmatically set the component property in the unit test.
我以编程方式在单元测试中设置了组件属性。 After calling
fixture.detectChanges()
, the component renders its DOM. 调用
fixture.detectChanges()
,组件将呈现其DOM。 However, what I am ngOnChanges()` function, I see it is never executed. 但是,我使用的是ngOnChanges()函数,我认为它永远不会执行。 What is the correct way to unit test this behavior?
对这种行为进行单元测试的正确方法是什么?
Here is my component: 这是我的组件:
@Component({
selector: 'field-value-map-item',
templateUrl: 'field-value-map-item.component.html',
styleUrls: [ 'field-value-map-item.component.less' ]
})
export class FieldValueMapItemComponent implements OnChanges {
@Input() data: FieldMapping
private mappings: { [id: number]: Array<ValueMapping> }
ngOnChanges(changes: any): void {
if (changes.data) { // && !changes.data.isFirstChange()) {
this.handleMappingDataChange(changes.data.currentValue)
}
}
private handleMappingDataChange(mapping: FieldMapping) {
// update mappings data structure
this.mappings = {};
if (mapping.inputFields) {
mapping.inputFields.forEach((field: Field) => {
field.allowedValues = sortBy(field.allowedValues, [ 'valueText' ])
})
}
if (mapping.valueMap) {
// console.log(mapping.valueMap.mappings.map((item) => item.input))
// order mappings by outputValue.valueText so that all target
// values are ordered.
let orderedMappings = sortBy(mapping.valueMap.mappings, [ 'inputValue.valueText' ])
orderedMappings.forEach((mapping) => {
if (!this.mappings[mapping.outputValue.id]) {
this.mappings[mapping.outputValue.id] = []
}
this.mappings[mapping.outputValue.id].push(mapping)
})
}
}
}
The component template: 组件模板:
<div class="field-value-map-item">
<div *ngIf="data && data.valueMap"
class="field-value-map-item__container">
<div class="field-value-map-item__source">
<avs-header-panel
*ngIf="data.valueMap['@type'] === 'static'"
[title]="data.inputFields[0].name">
<ol>
<li class="field-value-mapitem__value"
*ngFor="let value of data.inputFields[0].allowedValues">
{{value.valueText}}
</li>
</ol>
</avs-header-panel>
</div>
<div class="field-value-map-item__target">
<div *ngFor="let value of data.outputField.allowedValues">
<avs-header-panel [title]="value.valueText">
<div *ngIf="mappings && mappings[value.id]">
<div class="field-value-mapitem__mapped-value"
*ngFor="let mapping of mappings[value.id]">
{{mapping.inputValue.valueText}}
</div>
</div>
</avs-header-panel>
</div>
</div>
</div>
</div>
Here is my unit test: 这是我的单元测试:
describe('component: field-mapping/FieldValueMapItemComponent', () => {
let component: FieldValueMapItemComponent
let fixture: ComponentFixture<FieldValueMapItemComponent>
let de: DebugElement
let el: HTMLElement
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
FieldValueMapItemComponent
],
imports: [
CommonModule
],
providers: [ ],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
fixture = TestBed.createComponent(FieldValueMapItemComponent)
component = fixture.componentInstance
de = fixture.debugElement.query(By.css('div'))
el = de.nativeElement
})
afterEach(() => {
fixture.destroy()
})
describe('render DOM elements', () => {
beforeEach(() => {
spyOn(component, 'ngOnChanges').and.callThrough()
component.data = CONFIGURATION.fieldMappings[0]
fixture.detectChanges()
})
it('should call ngOnChanges', () => {
expect(component.ngOnChanges).toHaveBeenCalled() // this fails!
})
})
describe('sort output data values', () => {
beforeEach(() => {
component.data = CONFIGURATION.fieldMappings[1]
fixture.detectChanges()
})
it('should sort source field allowed values by their valueText property', () => {
let valueEls = el.querySelectorAll('.field-value-mapitem__value')
let valueText = map(valueEls, 'innerText')
expect(valueText.join(',')).toBe('Active,Foo,Terminated,Zip') // this fails
})
})
})
A solution to this problem is to introduce a simple Component
in the unit test. 解决此问题的方法是在单元测试中引入一个简单的
Component
。 This Component
contains the component in question as a child. 该
Component
包含有问题的组件作为子组件。 By setting input attributes on this child component, you will trigger the ngOnChanges()
function to be called just as it would in the application. 通过在此子组件上设置输入属性,您将触发
ngOnChanges()
函数的调用,就像在应用程序中一样。
Updated unit test (notice the simple @Component({...}) class ParentComponent
at the top). 更新了单元测试(请注意顶部的简单
@Component({...}) class ParentComponent
)。 In the applicable test cases, I instantiated a new test fixture and component (of type ParentComponent) and set the class attributes that are bound to the child components input attribute in order to trigger the ngOnChange()
. 在适用的测试案例中,我实例化了一个新的测试治具和组件(ParentComponent类型),并设置了绑定到子组件输入属性的类属性,以触发
ngOnChange()
。
@Component({
template: `
<div id="parent">
<field-value-map-item [data]="childData"></field-value-map-item>
</div>
`
})
class ParentComponent {
childData = CONFIGURATION.fieldMappings[1]
}
describe('component: field-mapping/FieldValueMapItemComponent', () => {
let component: FieldValueMapItemComponent
let fixture: ComponentFixture<FieldValueMapItemComponent>
let de: DebugElement
let el: HTMLElement
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
FieldValueMapItemComponent,
ParentComponent
],
imports: [
CommonModule
],
providers: [ ],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
fixture = TestBed.createComponent(FieldValueMapItemComponent)
component = fixture.componentInstance
de = fixture.debugElement.query(By.css('div'))
el = de.nativeElement
})
// ...
describe('sort output data values', () => {
let parentComponent: ParentComponent
let parentFixture: ComponentFixture<ParentComponent>
let parentDe: DebugElement
let parentEl: HTMLElement
beforeEach(() => {
parentFixture = TestBed.createComponent(ParentComponent)
parentComponent = parentFixture.componentInstance
parentDe = parentFixture.debugElement.query(By.css('#parent'))
parentEl = parentDe.nativeElement
parentFixture.detectChanges()
})
it('should sort source field allowed values by their valueText property', () => {
let valueEls = parentEl.querySelectorAll('.field-value-mapitem__value')
let valueText = map(valueEls, 'innerText')
expect(valueText.join(',')).toBe('Active,Foo,Terminated,Zip')
})
it('should sort target field mapped values by `inputValue.valueText`', () => {
parentComponent.childData = CONFIGURATION.fieldMappings[2]
parentFixture.detectChanges()
let valueEls = parentEl.querySelectorAll('.field-value-mapitem__mapped-value')
let valueText = map(valueEls, 'innerText')
expect(valueText.join(',')).toBe('BRC,FreeBurrito,FreeTaco')
})
})
})
The other way is calling ngOnChanges directly. 另一种方法是直接调用ngOnChanges。
it('should render `Hello World!`', () => {
greeter.name = 'World';
//directly call ngOnChanges
greeter.ngOnChanges({
name: new SimpleChange(null, greeter.name)
});
fixture.detectChanges();
expect(element.querySelector('h1').innerText).toBe('Hello World!');
});
ref: https://medium.com/@christophkrautz/testing-ngonchanges-in-angular-components-bbb3b4650ee8 参考: https : //medium.com/@christophkrautz/testing-ngonchanges-in-angular-components-bbb3b4650ee8
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.