【问题标题】:Angular 4: Mock ElementRefAngular 4:模拟 ElementRef
【发布时间】:2018-06-01 01:27:12
【问题描述】:

我试图弄清楚如何模拟注入到组件中的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.logapp.component.ts 的构造函数中的输出为:

如您所见,它注入的是MockAppService,而不是MockElementRef(尽管它们都以相同的方式模拟)。

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

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

【问题讨论】:

    标签: angular jasmine


    【解决方案1】:

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

    让我们逐步深入了解更长的答案,并尝试弄清楚 - 当我们通过 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);
      });
    

    在控制台中我们会看到:

    第一个控制台是从组件构造函数中记录的,第二个控制台是从测试中记录的。 所以,看起来我们正在处理ElementRef 的两个不同实例。让我们继续。

    第 2 步

    让我们看另一个例子,假设我们有一个注入 ElementRef 的组件和我们之前创建的其他一些自定义服务 AppService

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

    当我们测试这个组件时 - 我们必须提供 AppService(服务本身或其模拟),但是,如果我们不提供 ElementRefTestBed - 测试将永远不要抱怨这个:NullInjectorError: No provider for ElementRef!

    因此,我们可以建议,ElementRef 看起来不像依赖项,并且始终链接到组件本身。我们离答案越来越近了。 :)

    第 3 步

    让我们仔细看看TestBed 如何创建组件:TestBed.createComponent(AppComponent)。这是源代码的一个非常简化的版本:

    createComponent<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 类在source code 中的实现:

    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;
        // ...
    

    我们可以看到,elementRefComponentFixture 类的一个属性,它被初始化了构造函数。

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

    这就是为什么在组件构造函数console.log中我们看到ElementRef的实例而不是MockElementRef的实例。因此,我们在 TestBed 提供者数组中实际提供的只是基于 MockElementRefElementRef 的另一个实例。

    【讨论】:

      猜你喜欢
      • 2021-10-04
      • 1970-01-01
      • 1970-01-01
      • 2019-01-16
      • 1970-01-01
      • 1970-01-01
      • 2021-06-16
      • 2018-09-28
      • 2018-06-20
      相关资源
      最近更新 更多