4

How do you stub/mock a directive/component that is read as a ViewChild?

For example, using the simple directive from angular.io:

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  constructor() { }
}

Let's say that I am testing AppComponent and reads the HighlightDirective using ViewChild as:

@ViewChild(HighlightDirective) theHighlightDirective: HighlightDirective

And the stubbed directive is:

@Directive({
  selector: '[appHighlight]'
})
export class StubbedHighlightDirective {
  constructor() { }
}

Since the component is trying to read HighlightDirective, even if you declare StubbedHighlightDirective in your unit tests, theHighlightDirective will be undefined.

Example:

it('HighlightDirective is defined', () => {
    // This test fails
    expect(component.theHighlightDirective).toBeDefined();
});

You can get around this if you ignore some things in tslint or use the as keyword:

Version 1: Just ignore some things in tslint so compiler doesn't complain
it('HighlightDirective is defined', () => {
    // Compiler will typically complain saying that
    // StubbedHighlightDirective isn't assignable to type of HighlightDirective
    component.theHighlightDirective = new StubbedHighlightDirective();

    // this passes
    expect(component.theHighlightDirective).toBeDefined();
});

Version 2: Use "as" keyword
it('HighlightDirective is defined', () => {
    // Actually compiler will still complain with warnings
    component.theHighlightDirective = new StubbedHighlightDirective() as HighlightDirective;

    // this passes
    expect(component.theHighlightDirective).toBeDefined();
});

Is there another way to cleanly stub out these kinds of ViewChild references?

paulroho
  • 1,234
  • 1
  • 11
  • 27
yoonjesung
  • 1,148
  • 1
  • 9
  • 24

1 Answers1

5

The problem is that you're using a class to find the child, and that class has been replaced by your stub. You can use a matching exportAs link in your directive (docs, blog post) to make sure the real version and the stub have the same name.

In the original directive decorator:

@Directive({
    selector: '[appHighlight]',
    exportAs: 'appHighlight'
})
export class HighlightDirective {

In the stubbed directive:

@Directive({
    selector: '[appHighlight]',
    exportAs: 'appHighlight'
})
export class StubbedHighlightDirective {

And then in the template where the directive is used:

<div appHighlight #appHighlight="appHighlight">

With all this done you'll need to update your @ViewChild definition to use the string instead of the class:

@ViewChild('appHighlight') theHighlightDirective: HighlightDirective
Robin K
  • 51
  • 1