使用组件的 @ViewChild { read: ElementRef } 导致单元测试失败

2024-03-19

在我的组件中,我有一个如下所示的子组件:

<child-component #childComponent></childComponent>

然后,在我的父组件中,我使用 @ViewChild 和read参数来获取 ElementRef,而不是组件引用。我需要 ElementRef 以确保我可以从中获取一些属性nativeElement我需要的。所以它是这样的:

export class ParentComponent {
  @ViewChild('childComponent', { read: ElementRef }) public childComponent: ElementRef;
  public position: string;

  // some way down the code
  private someMethod() {
    if (this.childComponent.nativeElement.offsetLeft > 500) {
      this.position = 'left';
    } else {
      this.position = 'right';
    }
  }
}

所以这适用于应用程序,但是我正在编写测试并模拟子组件,如下所示:

@Component({
  selector: 'child-component',
  template: ''
})
class ChildComponentMockComponent {
  private nativeElement = {
    get offsetLeft() {
      return 600
    }
  };
}

beforeEach(async(() => TestBed.configureTestingModule({
  imports: [ ... ],
  declarations: [ ParentComponent, ChildComponentMockComponent ],
  providers: [ ... ],
  schemas: [ NO_ERRORS_SCHEMA ]
}).compileComponents()));

it('should have the correct position, based on position of child-component', () => {
  spyOn(component, 'someMethod');
  expect(component.someMethod).toHaveBeenCalled();
  expect(component.position).toBe('left');
});

因此,测试将编译组件,并使用模拟的子组件值作为正确的值并计算this.position,然后在测试中断言。

然而,当{ read: ElementRef }设置参数后,模拟将被 TestBed 完全忽略,即使它已添加到声明数组中。如果我删除{ read: ElementRef },mock用于测试并且通过了。但是我的应用程序不起作用,因为它现在正在获取组件引用,其中nativeElement属性不存在,而不是元素引用不存在。

那么如何在应用程序中获取 ElementRef,然后在测试中使用模拟组件呢?


我通过更改应用程序的架构解决了这个问题。子组件现在找到它自己的 offsetLeft 属性,然后将其放入输出 EventEmitter 中以供父组件拾取。

export class ChildComponent implements AfterViewInit {
  @Output() offsetPosition: EventEmitter<number> = new EventEmitter<number>();

  constructor(private el: ElementRef) {}

  public ngAfterViewInit() {
    this.offsetPosition.emit(this.el.nativeElement.offsetLeft);
  }
}

export class ParentComponent implements AfterViewInit {
  public childComponentOffset: number;

  public ngAfterViewInit() {
    setTimeout(() => {
      // this.childComponentOffset is available
      // needs to be in setTimeout to prevent ExpressionChangedAfterItHasBeenCheckedError
      // more info: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
    }
  }

  public getChildComponentOffset(position: number): void {
    this.childComponentOffset = position;
  }
}

然后在 HTML 中,您只需使用输出变量和方法定义子组件:

<child-component (offsetPosition)="getChildComponentOffset($event)"></child-component>

在测试中,我随后模拟子组件的 ElementRef 并将其用作提供程序。

const mockElementRef: any = {
  get offsetLeft() {
    return position;
  }
};

beforeEach(async(() => TestBed.configureTestingModule({
  imports: [ ... ],
  declarations: [ ParentComponent ],
  providers: [
    { provide: ElementRef, useValue: mockElementRef }
  ],
  schemas: [ NO_ERRORS_SCHEMA ]
}).compileComponents()));

it('should have the correct position, based on position of child-component', (done) => {
  component.getChildComponentOffset(600);
  setTimeout(() => expect(component.position).toBe('left'));
  done();
});
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用组件的 @ViewChild { read: ElementRef } 导致单元测试失败 的相关文章

随机推荐