How to test a ViewChild / ViewChildren of a declaration
When we want to test a ViewChild
or ViewChildren
,
or a logic which depends on them,
it means, that we have child dependencies which derive from components or directives.
Therefore, the best way to write a test is to use their mock objects in the test.
Let's assume that we want to test a TargetComponent
and its code looks like:
@Component({
selector: 'target',
templateUrl: './target.component.html',
})
class TargetComponent {
@ViewChild(DependencyComponent)
public component?: DependencyComponent;
@ViewChild(DependencyComponent, {
read: DependencyDirective,
})
public directive?: DependencyDirective;
@ViewChildren(DependencyDirective)
public directives?: QueryList<DependencyDirective>;
@ViewChild('tpl')
public tpl?: TemplateRef<HTMLElement>;
public value = '';
}
and its template is the next:
<dependency
[dependency]="0"
(trigger)="value = $event"
></dependency>
<div>
<span
[dependency]="1"
(trigger)="value = $event"
>1</span>
<span
[dependency]="2"
(trigger)="value = $event"
>2</span>
<span
[dependency]="3"
(trigger)="value = $event"
>3</span>
</div>
<ng-template #tpl>
{{ value }}
</ng-template>
In this case, we can use MockBuilder
,
Let's pass TargetComponent
as the first parameter, and its module
as the second one into MockBuilder
.
Then DependencyComponent
and DependencyDirective
will be replaced
with their mocks, therefore we can use fake emits and provide custom behavior if it is needed.
// do not forget to return
// the result of MockBuilder
beforeEach(() => MockBuilder(
TargetComponent,
TargetModule,
));
In the test, we can access the mock declarations via normal queries
or via ngMocks.findInstance
.
For example, if we wanted to simulate an emit of the child component, we could call it like that:
ngMocks
.findInstance(DependencyComponent)
.trigger
.emit('component');
Live example
import {
Component,
Directive,
EventEmitter,
Input,
NgModule,
Output,
QueryList,
TemplateRef,
ViewChild,
ViewChildren,
} from '@angular/core';
import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';
@Component({
selector: 'child',
template: 'child',
})
class ChildComponent {
@Input() public child: number | null = null;
@Output() public readonly trigger = new EventEmitter<string>();
}
@Directive({
selector: '[child]',
})
class ChildDirective {
@Input() public child: number | null = null;
@Output() public readonly trigger = new EventEmitter<string>();
}
@Component({
selector: 'target',
template: `
<child
[child]="0"
(trigger)="value = $event"
></child>
<div>
<span [child]="1" (trigger)="value = $event">1</span>
<span [child]="2" (trigger)="value = $event">2</span>
<span [child]="3" (trigger)="value = $event">3</span>
</div>
<ng-template #tpl>
{{ value }}
</ng-template>
`,
})
class TargetComponent {
@ViewChild(ChildComponent)
public component?: ChildComponent;
@ViewChild(ChildComponent, {
read: ChildDirective,
})
public directive?: ChildDirective;
@ViewChildren(ChildDirective)
public directives?: QueryList<ChildDirective>;
@ViewChild('tpl')
public tpl?: TemplateRef<HTMLElement>;
public value = '';
}
@NgModule({
declarations: [
ChildComponent,
ChildDirective,
TargetComponent,
],
exports: [TargetComponent],
})
class TargetModule {}
describe('TestViewChild', () => {
beforeEach(() => MockBuilder(TargetComponent, TargetModule));
it('provides mock dependencies', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.point.componentInstance;
// checking @ViewChild(ChildComponent)
expect(component.component).toEqual(
jasmine.any(ChildComponent),
);
expect(
component.component && component.component.child,
).toEqual(0);
// checking its read: ChildDirective
expect(component.directive).toEqual(
jasmine.any(ChildDirective),
);
expect(
component.directive && component.directive.child,
).toEqual(0);
// checking TemplateRef
expect(component.tpl).toEqual(jasmine.any(TemplateRef));
// @ViewChildren(ChildDirective)
if (!component.directives) {
throw new Error('component.directives are missed');
}
expect(component.directives.length).toEqual(4);
const [dir0, dir1, dir2, dir3] = component.directives.toArray();
expect(dir0 && dir0.child).toEqual(0);
expect(dir1 && dir1.child).toEqual(1);
expect(dir2 && dir2.child).toEqual(2);
expect(dir3 && dir3.child).toEqual(3);
// asserting outputs of ChildComponent
expect(component.value).toEqual('');
ngMocks
.findInstance(ChildComponent)
.trigger.emit('component');
expect(component.value).toEqual('component');
// asserting outputs ChildDirective
ngMocks
.findInstances(ChildDirective)[0]
.trigger.emit('dir0');
expect(component.value).toEqual('dir0');
ngMocks
.findInstances(ChildDirective)[1]
.trigger.emit('dir1');
expect(component.value).toEqual('dir1');
ngMocks
.findInstances(ChildDirective)[2]
.trigger.emit('dir2');
expect(component.value).toEqual('dir2');
ngMocks
.findInstances(ChildDirective)[3]
.trigger.emit('dir3');
expect(component.value).toEqual('dir3');
});
});