【问题标题】:Using @ViewChild { read: ElementRef } of component causes unit test to fail使用组件的 @ViewChild { read: ElementRef } 会导致单元测试失败
【发布时间】:2018-05-20 06:29:44
【问题描述】:

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

<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 },则在测试中使用模拟并通过。但后来我的应用程序无法工作,因为它现在正在获取组件引用,其中 nativeElement 属性不存在,而不是元素引用。

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

【问题讨论】:

  • 你能把你写的测试用例加进去,我帮你修一下
  • 嗨@Aravind,该属性实际上并未在测试中使用,抱歉含糊不清。但是测试使用模拟的子组件而不是正确的子组件来编译组件,然后在组件的应用程序代码中,它使用我在模拟中设置的值而不是其他任何东西。应用程序代码根据子组件的位置设置一个变量(真/假),然后我在测试中对其进行断言。这很令人困惑,但我在原始帖子中添加了更多细节。
  • 当你使用read elementRef时,它并没有使用组件实例,那你为什么要在组件实例上声明nativeElement属性呢?你想模拟elementRef吗?
  • nativeElement 在组件实例上不可用。理想情况下是的,我会模拟 elementRef,但我尝试这样做,但它似乎没有用。

标签: javascript angular unit-testing


【解决方案1】:

我已通过更改应用程序的架构来解决此问题。子组件现在找到自己的 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();
});

【讨论】:

    猜你喜欢
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    • 2021-05-08
    • 2017-07-04
    • 1970-01-01
    • 2019-01-02
    • 1970-01-01
    相关资源
    最近更新 更多