Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Directive ViewContainerRef Test Mock

Given a directive used for Dynamic Component Loading with ViewContainerRef injected:

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[fooHost]'
})
export class FooDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

How would you inject an instance or mock of ViewContainerRef in a unit test:

import { FooDirective } from './foo.directive';

describe('FooDirective', () => {
  it('should create an instance', () => {
    const directive = new FooDirective();
    expect(directive).toBeTruthy();
  });
});

This most basic test fails due to the following error:

An argument for 'viewContainerRef' was not provided.

The testing guide does not cover this nor does there appear to be any testing module specifically for creating an instance of ViewContainerRef.

Is this is simple as creating a stub @Component with TestBed.createComponent and passing either the fixture or component instance as ViewContainerRef?

import { FooDirective } from './foo.directive';
import { ViewContainerRef, Component } from '@angular/core';
import { TestBed, async } from '@angular/core/testing';

@Component({ selector: 'app-stub', template: '' })
class StubComponent {}

describe('LightboxDirective', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({ declarations: [StubComponent] }).compileComponents();
  }));

  it('should create an instance', () => {
    const fixture = TestBed.createComponent(StubComponent);
    const component = fixture.debugElement.componentInstance;

    const directive = new FooDirective(component);
    expect(directive).toBeTruthy();
  });
});

If that is the case, what should be passed as ViewContainerRef, the fixture.debugElement.componentInstance or fixture.debugElement.nativeElement or something else?

Thanks!

like image 998
Alexander Staroselsky Avatar asked Jun 30 '19 21:06

Alexander Staroselsky


1 Answers

ViewContainerRef is an abstract class that is imported from @angular/core. Because it is an abstract class, it cannot be directly instantiated. However, in your test class, you can simply create a new class which extends the ViewContainerRef, and implements all of the required methods. Then, you can simply instantiate a new instance of the TestViewContainerRef and pass it into your FooDirective constructor in your test/spec. As such:

// create the test class
class TestViewContainerRef extends ViewContainerRef {
  get element(): import("@angular/core").ElementRef<any> {
    throw new Error("Method not implemented.");
  }
  get injector(): import("@angular/core").Injector {
    throw new Error("Method not implemented.");
  }
  get parentInjector(): import("@angular/core").Injector {
    throw new Error("Method not implemented.");
  }
  clear(): void {
    throw new Error("Method not implemented.");
  }
  get(index: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }
  get length(): number {
    throw new Error("Method not implemented.");
  }
  createEmbeddedView<C>(templateRef: import("@angular/core").TemplateRef<C>, context?: C, index?: number): import("@angular/core").EmbeddedViewRef<C> {
    throw new Error("Method not implemented.");
  }
  createComponent<C>(componentFactory: import("@angular/core").ComponentFactory<C>, index?: number, injector?: import("@angular/core").Injector, projectableNodes?: any[][], ngModule?: import("@angular/core").NgModuleRef<any>): import("@angular/core").ComponentRef<C> {
    throw new Error("Method not implemented.");
  }
  insert(viewRef: import("@angular/core").ViewRef, index?: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }
  move(viewRef: import("@angular/core").ViewRef, currentIndex: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }
  indexOf(viewRef: import("@angular/core").ViewRef): number {
    throw new Error("Method not implemented.");
  }
  remove(index?: number): void {
    throw new Error("Method not implemented.");
  }
  detach(index?: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }

}

Hint: I am using VS Code on a Mac. When I create the class stub class TestViewContainerRef extends ViewContainerRef { }, Code gives me a very helpful code hint to implement all abstract methods. I used that to automatically generate the code above. Other IDEs may offer similar functionality to help the process go smoother. You may be able to copy/paste the code here to use in your test/spec class. However, Angular may choose to change the interface for the ViewContainerRef abstract class at any time, so if you do copy this code above, be aware that you do so at your own peril.

Here's an example of how I used it to get my Angular tests to pass:

import { ModalHostDirective } from './modal-host.directive';
import { ViewContainerRef } from '@angular/core';

class TestViewContainerRef extends ViewContainerRef {
  get element(): import("@angular/core").ElementRef<any> {
    throw new Error("Method not implemented.");
  }
  get injector(): import("@angular/core").Injector {
    throw new Error("Method not implemented.");
  }
  get parentInjector(): import("@angular/core").Injector {
    throw new Error("Method not implemented.");
  }
  clear(): void {
    throw new Error("Method not implemented.");
  }
  get(index: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }
  get length(): number {
    throw new Error("Method not implemented.");
  }
  createEmbeddedView<C>(templateRef: import("@angular/core").TemplateRef<C>, context?: C, index?: number): import("@angular/core").EmbeddedViewRef<C> {
    throw new Error("Method not implemented.");
  }
  createComponent<C>(componentFactory: import("@angular/core").ComponentFactory<C>, index?: number, injector?: import("@angular/core").Injector, projectableNodes?: any[][], ngModule?: import("@angular/core").NgModuleRef<any>): import("@angular/core").ComponentRef<C> {
    throw new Error("Method not implemented.");
  }
  insert(viewRef: import("@angular/core").ViewRef, index?: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }
  move(viewRef: import("@angular/core").ViewRef, currentIndex: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }
  indexOf(viewRef: import("@angular/core").ViewRef): number {
    throw new Error("Method not implemented.");
  }
  remove(index?: number): void {
    throw new Error("Method not implemented.");
  }
  detach(index?: number): import("@angular/core").ViewRef {
    throw new Error("Method not implemented.");
  }

}

describe('ModalHostDirective', () => {
  it('should create an instance', () => {
    const directive = new ModalHostDirective(new TestViewContainerRef());
    expect(directive).toBeTruthy();
  });
});

Disclaimer: As far as actually writing tests against this TestViewContainerRef, well...I leave that to all of you. But this at least satisfies ng test.

Cheers!

like image 181
Danny Bullis Avatar answered Sep 18 '22 09:09

Danny Bullis