Angular 4:Mock ElementRef

时间:2017-12-18 16:51:15

标签: angular jasmine

我正在试图弄清楚如何模拟注入组件的ElementRef。我的组件如下:

app.component.ts:

import { Component, ElementRef } from '@angular/core';

import { AppService } from './app.service';

@Component({
  selector: 'app-root',
  templateUrl: './app/app.component.html',
  styleUrls: ['./app/app.component.css']
})
export class AppComponent {
  title = 'app works!';

  constructor(private _elementRef: ElementRef, private _appService: AppService) {
    console.log(this._elementRef);
    console.log(this._appService);
  }
}

和我的测试规范如下:

app.component.spec.ts:

import { TestBed, async } from '@angular/core/testing';
import { ElementRef, Injectable } from '@angular/core';
import { AppComponent } from './app.component';
import { AppService } from './app.service';

@Injectable()
export class MockElementRef {
  nativeElement: {}  
}

@Injectable()
export class MockAppService {

}

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [
        {provide: ElementRef, useClass: MockElementRef},
        {provide: AppService, useClass: MockAppService}
      ]
    }).compileComponents();
  }));

  ...
});

运行测试时,console.log的构造函数中app.component.ts的输出为:

console output

正如你所看到的,它注入的是MockAppService而不是MockElementRef(即使它们都以同样的方式被嘲笑)。

这个SO post suggests可以像其他任何模拟一样进行设置,但是我注意到这是针对Angular 2的 - 所以我想知道Angular 4中是否有变化?

可以找到具有上述代码和Jasmine测试的Plunker here。运行Plunker然后单击“Run Unit Tests”链接以启动单元测试。可以在开发人员工具/ Firebug中观察控制台输出。

1 个答案:

答案 0 :(得分:2)

简短答案-这是设计使然:)

让我们逐步深入研究更长的答案,并尝试弄清楚-当我们通过TestBed配置测试模块时,到底发生了什么。

第1步

根据test_bed.ts的源代码:

configureTestingModule(moduleDef: TestModuleMetadata): void {
  if (moduleDef.providers) {
    this._providers.push(...moduleDef.providers);
  }
  if (moduleDef.declarations) {
    this._declarations.push(...moduleDef.declarations);
  }
  // ...
}

我们可以看到-configureTestingModule方法只是将提供的实例推入this._providers数组中。然后我们可以说:嘿,TestBed,请给我这个提供者ElementRef

  // ...
  let elRef: ElementRef;

  beforeEach(() => {
    TestBed.configureTestingModule({
      // ...
      providers: [{provide: ElementRef, useValue: new MockElementRef()}]
    });

    // ...
    elRef = TestBed.get(ElementRef);
  });

  it('test', () => {
    console.log(elRef);
  });

在控制台中,我们将看到:

enter image description here

第一个控制台是从组件构造函数记录的,而第二个控制台是从测试记录的。 因此,似乎我们正在处理ElementRef 的两个不同实例。让我们继续前进。

第2步

让我们看另一个示例,并说我们有一个注入ElementRef和我们之前创建的其他自定义服务AppService的组件:

export class HelloComponent  {
  constructor(private _elementRef: ElementRef, private _appService: AppService) {
    console.log(this._elementRef);
    console.log(this._appService);
  }
}

我们在测试此组件时-如果不向{{1提供AppService,则必须提供ElementRef(服务本身或其模拟),但是 }}-测试永远不会抱怨:TestBed

因此,我们可以建议NullInjectorError: No provider for ElementRef!看起来不像是依赖项,而是始终链接到组件本身。我们越来越接近答案。 :)

第3步

让我们仔细研究ElementRef如何创建组件:TestBed。这是来自源代码的非常简化的版本:

TestBed.createComponent(AppComponent)

因此,我们必须继续检查source codecreateComponent<T>(component: Type<T>): ComponentFixture<T> { this._initIfNeeded(); const componentFactory = this._compiler.getComponentFactory(component); // ... const componentRef = componentFactory.create(Injector.NULL, [], `#${rootElId}`, this._moduleRef); return new ComponentFixture<T>(componentRef, ngZone, autoDetect); // ... } 类的实现:

ComponentFixture

我们可以看到export class ComponentFixture<T> { // The DebugElement associated with the root element of this component. debugElement: DebugElement; // The instance of the root component class. componentInstance: T; // The native element at the root of the component. nativeElement: any; // The ElementRef for the element at the root of the component. elementRef: ElementRef; // ... constructor( public componentRef: ComponentRef<T>, public ngZone: NgZone|null, private _autoDetect: boolean) { this.changeDetectorRef = componentRef.changeDetectorRef; this.elementRef = componentRef.location; // ... elementRef类的属性,该类已初始化构造函数。

最后,总结以上内容-我们得到了答案:ComponentFixture实际上是DOM元素的包装,它被注入构造函数中的组件。 ElementRef的注入实例是对当前组件的宿主元素的引用。请遵循此StackOverflow post以获得有关它的更多信息。

这就是为什么在组件构造器console.log中我们看到ElementRef实例而不是ElementRef实例的原因。因此,我们在TestBed provider数组中实际提供的内容只是基于MockElementRef的{​​{1}}的另一个实例。