I want to test an Angular Component and replace the contentChild with a Mock. I followed this guide: https://medium.com/angular-in-depth/angular-unit-testing-viewchild-4525e0c7b756 The guide is for a viewChild, but I thought this could also work for a content Child.
My contentChild has a Observable to which the parent subscribe. If I test the code with the real child, it works. If I mock the child, the test doesn't work. I think the child mock, I query in the test to emit a new value, is a different instance, than the one which the parent subscribed to.
My parent:
@Component({
// tslint:disable-next-line:component-selector
selector: '[appParent]',
template: `
<div>
<span class="true" *ngIf="display$ | async; else other">True</span>
</div>
<ng-content></ng-content>
<ng-template #other><span class="false">False</span></ng-template>
`
})
export class ParentComponent implements AfterContentInit {
@ContentChild(ChildComponent) child!: ChildComponent;
display$: Observable<boolean>;
ngAfterContentInit(): void {
this.display$ = this.child.display$;
}
}
My mock:
@Component({
selector: 'app-child',
template: '',
providers: [
{
provide: ChildComponent,
useExisting: ChildStubComponent
}
]
})
export class ChildStubComponent {
displaySubject = new BehaviorSubject(false);
display$: Observable<boolean> = this.displaySubject.asObservable();
}
And the test:
describe('ParentComponentTest', () => {
@Component({
template: `
<div appParent>
<app-child></app-child>
</div>
`
})
class TestComponent {
@ViewChild(ChildStubComponent)
child!: ChildStubComponent;
}
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TestComponent,
ChildStubComponent,
ParentComponent
]
}).compileComponents();
});
beforeEach(async () => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should find true', () => {
component.child.displaySubject.next(true);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.true'))).toBeTruthy();
});
});
The problem seams to be, that you get with the view-child not the correct instance. The solution is to get the instance from the child property of the parent component.
describe('ParentComponentTest', () => {
@Component({
template: `
<div appParent>
<app-child></app-child>
</div>
`
})
class TestComponent {
@ViewChild(ParentComponent)
parent!: ParentComponent;
}
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
....
it('should find true', () => {
const childStub = component.parent.child.toArray()[0] as unknown as ChildStubComponent;
childStub.displaySubject.next(true);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.true'))).toBeTruthy();
});
});