web-dev-qa-db-ja.com

Angular 4:モックElementRef

コンポーネントにインジェクトされる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の投稿が示唆する 他のモックと同じように設定しますが、これはAngular 2- in Angular 4?

上記のコードとJasmineテストを含むPlunkerは here にあります。プランカーを実行し、ユニットテストを開始するために「ユニットテストを実行」リンクをクリックします。コンソール出力は、開発者ツール/ Firebugで確認できます。

12
Ian A

短い答え-それは設計による:)

より長い回答を一歩ずつ掘り下げて理解してみましょう-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

最初のコンソールはコンポーネントコンストラクターから記録され、2番目のコンソールはテストから記録されました。だから、2つのコンソールElementRefの異なるインスタンス。次へ移りましょう。

ステップ2

別の例を見てみましょう。ElementRefと以前に作成した他のカスタムサービスAppServiceを注入するコンポーネントがあるとします。

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

このコンポーネントをテストするとき-AppService(サービス自体またはそのモック)、[〜#〜] but [〜#〜]を提供する必要があります。ElementRefTestBedに提供しない場合これについて文句を言うことはありません:_NullInjectorError: No provider for ElementRef!_。

したがって、ElementRefは依存関係のようには見えず、常にコンポーネント自体にリンクされていると提案できます。答えに近づいています。 :)

ステップ

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クラスの実装を確認する必要があります。

_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クラスのプロパティであることがわかります。

最後に、上記の要約-答えがあります:コンストラクターのコンポーネントに注入されるElementRefは、実際にはDOM要素のラッパーです。挿入されたElementRefのインスタンスは、現在のコンポーネントのHost要素への参照です。詳細については、この StackOverflow post に従ってください。

これが、コンポーネントコンストラクターconsole.logでElementRefのインスタンスではなく、MockElementRefのインスタンスを見る理由です。したがって、TestBedプロバイダー配列で実際に提供したのは、ElementRefに基づくMockElementRefの単なる別のインスタンスです。

3
shohrukh