-
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): ViewContainerRef.createComponent respects mocks #4742
- Loading branch information
Showing
5 changed files
with
194 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
--- | ||
title: How to mock a dynamic component | ||
description: Mocking an Angular dynamic component | ||
sidebar_label: Dynamic Components | ||
--- | ||
|
||
Angular has introduced a way how to render components dynamically. | ||
Now, it can be done via `ViewContainerRef.createComponent(DynamicComponent)`, | ||
and components, which are rendering dynamic components, usually, look like: | ||
|
||
```ts | ||
@Component({ | ||
standalone: true, | ||
selector: 'main', | ||
template: '', | ||
}) | ||
export class MainComponent implements OnInit { | ||
// ViewContainerRef is needed to manager rendering | ||
constructor(public readonly containerRef: ViewContainerRef) {} | ||
|
||
async ngOnInit() { | ||
// loading DynamicComponent | ||
const { DynamicComponent } = await import('./dynamic.component'); | ||
|
||
// rendering DynamicComponent | ||
this.containerRef.createComponent(DynamicComponent); | ||
} | ||
} | ||
``` | ||
|
||
In unit tests, developers might need to mock `DynamicComponent` to relieve testing. | ||
Their goal is to assert that `MainComponent` has rendered `DynamicComponent` under defined circumstances | ||
and suppress what `DynamicComponent` does under the hood. | ||
|
||
This can be achieved with help of `ng-mocks` and [`MockBuilder`](../../api/MockBuilder.md), | ||
simply pass `DynamicComponent` as mock dependency: | ||
|
||
```ts | ||
beforeEach(() => MockBuilder(MainComponent, DynamicComponent)); | ||
``` | ||
|
||
In this case, `ng-mocks` will mock `DynamicComponent` and render its stub. | ||
|
||
:::tip | ||
`ng-mocks` intercepts the call of `ViewContainerRef.createComponent()`, not `import()`. | ||
::: | ||
|
||
|
||
## An example how to mock dynamic components | ||
|
||
```ts | ||
import { Component, OnInit, ViewContainerRef } from '@angular/core'; | ||
import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; | ||
|
||
import { DynamicComponent } from './dynamic.component'; | ||
|
||
@Component({ | ||
standalone: true, | ||
selector: 'main', | ||
template: '', | ||
}) | ||
class MainComponent implements OnInit { | ||
// ViewContainerRef is needed to manager rendering | ||
constructor(public readonly containerRef: ViewContainerRef) {} | ||
|
||
async ngOnInit() { | ||
// loading DynamicComponent | ||
const { DynamicComponent } = await import('./dynamic.component'); | ||
|
||
// rendering DynamicComponent | ||
this.containerRef.createComponent(DynamicComponent); | ||
} | ||
} | ||
|
||
describe('suite', () => { | ||
beforeEach(() => MockBuilder(MainComponent, DynamicComponent)); | ||
|
||
it('loads lazy component as a mock', async () => { | ||
// loading the MainComponent and waiting for its initialization | ||
const fixture = MockRender(MainComponent); | ||
await fixture.whenStable(); | ||
|
||
// asserting that DynamicComponent has been rendered | ||
const el = ngMocks.find(DynamicComponent, undefined); | ||
expect(el).toBeDefined(); | ||
}); | ||
}); | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Component } from '@angular/core'; | ||
|
||
@Component({ | ||
standalone: true, | ||
selector: 'child', | ||
template: 'child', | ||
}) | ||
export class ChildComponent {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Component, OnInit, ViewContainerRef } from '@angular/core'; | ||
import { isMockOf, MockBuilder, MockRender, ngMocks } from 'ng-mocks'; | ||
|
||
import { ChildComponent } from './child.component'; | ||
|
||
@Component({ | ||
standalone: true, | ||
selector: 'target', | ||
template: '', | ||
}) | ||
class TargetComponent implements OnInit { | ||
constructor(public readonly containerRef: ViewContainerRef) {} | ||
|
||
async ngOnInit() { | ||
const { ChildComponent } = await import('./child.component'); | ||
this.containerRef.createComponent(ChildComponent); | ||
} | ||
} | ||
|
||
describe('issue-4693', () => { | ||
describe('real', () => { | ||
beforeEach(() => MockBuilder(TargetComponent)); | ||
|
||
it('loads lazy component', async () => { | ||
const fixture = MockRender(TargetComponent); | ||
await fixture.whenStable(); | ||
const el = ngMocks.find(ChildComponent); | ||
expect(ngMocks.formatText(el)).toEqual('child'); | ||
expect(isMockOf(el.componentInstance, ChildComponent)).toEqual( | ||
false, | ||
); | ||
}); | ||
}); | ||
|
||
describe('mock', () => { | ||
beforeEach(() => MockBuilder(TargetComponent, ChildComponent)); | ||
|
||
it('loads lazy component as a mock', async () => { | ||
const fixture = MockRender(TargetComponent); | ||
await fixture.whenStable(); | ||
const el = ngMocks.find(ChildComponent); | ||
expect(ngMocks.formatText(el)).toEqual(''); | ||
expect(isMockOf(el.componentInstance, ChildComponent)).toEqual( | ||
true, | ||
); | ||
}); | ||
}); | ||
}); |