我有一个相当简单的工作Angular 2组件。该组件根据我已排序的值数组呈现一些div兄弟元素。该组件实现OnChanges
。排序功能在执行ngOnChanges()
期间发生。最初,我的@Input()
属性引用未排序的值数组。一旦更改检测启动,生成的DOM元素将按预期排序。
我编写了一个Karma单元测试来验证组件中的排序逻辑是否已经发生,并且预期的DOM元素是按排序顺序呈现的。我以编程方式在单元测试中设置组件属性。调用fixture.detectChanges()
后,组件呈现其DOM。但是,我是ngOnChanges()`函数,我看它永远不会被执行。对这种行为进行单元测试的正确方法是什么?
这是我的组件:
@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)
})
}
}
}
组件模板:
<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>
这是我的单元测试:
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
})
})
})
答案 0 :(得分:0)
此问题的解决方案是在单元测试中引入简单的Component
。此Component
包含作为子项的相关组件。通过在此子组件上设置输入属性,您将触发调用ngOnChanges()
函数,就像在应用程序中一样。
更新了单元测试(注意顶部的简单@Component({...}) class ParentComponent
)。在适用的测试用例中,我实例化了一个新的测试夹具和组件(类型为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')
})
})
})
答案 1 :(得分:0)
另一种方法是直接调用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!');
});
参考:https://medium.com/@christophkrautz/testing-ngonchanges-in-angular-components-bbb3b4650ee8