How to mock components in Angular tests
A mock component in Angular tests can be created by MockComponent
function.
The mock component respects the interface of its original component,
but all its methods are dummies.
To create a mock component, simply pass its class into MockComponent
function.
TestBed.configureTestingModule({
declarations: [
// for a single component
MockComponent(Component),
// for a set of components
...MockComponents(Component1, Component2),
],
});
The class of a mock component has:
- the same
selector
- the same
@Inputs
and@Outputs
with alias support - templates with pure
<ng-content>
tags to allow transclusion - support for
@ContentChild
and@ContentChildren
- support for
ControlValueAccessor
,Validator
andAsyncValidator
- support for
exportAs
- support for standalone components
Information about mocking FormControl, ControlValueAccessor, Validator and AsyncValidator is in a different section.
Simple example
Let's pretend that in our Angular application TargetComponent
depends on a child component of DependencyComponent
and we want to use its mock object in a test.
Usually beforeEach
looks like:
describe('Test', () => {
let component: TargetComponent;
let fixture: ComponentFixture<TargetComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
// our component for testing
TargetComponent,
// the annoying dependency
DependencyComponent,
],
});
fixture = TestBed.createComponent(TargetComponent);
component = fixture.componentInstance;
});
});
To create a mock child component, simply pass its class into MockComponent
:
TestBed.configureTestingModule({
declarations: [
TargetComponent,
// profit
MockComponent(DependencyComponent),
],
});
Or be like a pro and use MockBuilder
, its .mock
method
and MockRender
:
describe('Test', () => {
beforeEach(() => {
// DependencyComponent is a declaration or imported somewhere in ItsModule.
return MockBuilder(TargetComponent, ItsModule);
});
it('should create', () => {
const fixture = MockRender(TargetComponent);
expect(fixture.point.componentInstance).toBeDefined();
});
});
Standalone components
Starting Angular 14, it provides support for standalone components.
They are supported by ng-mocks
. To mock a standalone component, you need to call MockComponent
in imports:
TestBed.configureTestingModule({
imports: [
// for a single component
MockComponent(StandaloneComponent),
// for a set of components
...MockComponents(Standalone1Component, Standalone2Component),
],
declarations: [
// our component for testing
TargetComponent,
],
});
MockBuilder
also supports standalone components
and allows to mock their imports only for shallow rendering.
Advanced example
An advanced example about mocking components. Please, pay attention to comments in the code.
describe('MockComponent', () => {
beforeEach(() => {
return MockBuilder(TargetComponent, ItsModule);
});
it('sends the correct value to the child input', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.point.componentInstance;
// The same as
// fixture.debugElement.query(
// By.css('child')
// ).componentInstance
// but properly typed.
const mockComponent = ngMocks.find<ChildComponent>(
'child',
).componentInstance;
// Let's pretend that DependencyComponent has 'someInput' as
// an input. MyComponent sets its value via
// `[someInput]="value"`. The input's value will be passed into
// the mock component so we can assert on it.
component.value = 'foo';
fixture.detectChanges();
// Thanks to ng-mocks, this is type safe.
expect(mockComponent.someInput).toEqual('foo');
});
it('does something on an emit of the child component', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.point.componentInstance;
// The same as
// fixture.debugElement.query(
// By.directive(DependencyComponent)
// ).componentInstance
// but properly typed.
const mockComponent = ngMocks.findInstance(ChildComponent);
// Again, let's pretend DependencyComponent has an output
// called 'someOutput'. MyComponent listens on the output via
// `(someOutput)="trigger($event)"`.
// Let's install a spy and trigger the output.
ngMocks.stubMember(
component,
'trigger',
typeof jest === 'undefined' ? jasmine.createSpy() : jest.fn(),
);
mockComponent.someOutput.emit({
payload: 'foo',
});
// Assert on the effect.
expect(component.trigger).toHaveBeenCalledWith({
payload: 'foo',
});
});
it('renders something inside of the child component', () => {
const localFixture = MockRender<ChildComponent>(`
<child>
<p>inside content</p>
</child>
`);
// We can access html directly asserting on some side effect.
const mockNgContent = localFixture.point.nativeElement.innerHTML;
expect(mockNgContent).toContain('<p>inside content</p>');
});
it('renders ContentChild of the child component', () => {
const fixture = MockRender<ChildComponent>(`
<child>
<ng-template #something>
<p>inside template</p>
</ng-template>
<p>inside content</p>
</child>
`);
// Injected ng-content rendered everything except templates.
const mockNgContent = fixture.point.nativeElement.innerHTML;
expect(mockNgContent).toContain('<p>inside content</p>');
expect(mockNgContent).not.toContain('<p>inside template</p>');
// Let's render the template. First, we need to assert that
// componentInstance is a MockedComponent<T> to access
// its `__render` method. `isMockOf` function helps here.
const mockComponent = fixture.point.componentInstance;
ngMocks.render(
mockComponent,
ngMocks.findTemplateRef('something'),
);
// The rendered template is wrapped by <div data-key="something">.
// We can use this selector to assert exactly its content.
const mockNgTemplate =
ngMocks.find(ChildComponent).nativeElement.innerHTML;
expect(mockNgTemplate).toContain('<p>inside template</p>');
});
});