Skip to main content

How to test a standalone component in Angular and mock its `imports`

This section describes how to test a standalone component.

Usually, developers want to mock all dependencies. For a standalone component, it means all its imports. This behavior is possible to achieve with MockBuilder.

Let's imagine we have the following standalone component:

@Component({
selector: 'target',
template: `<dependency>{{ name | standalone }}</dependency>`,
standalone: true,
imports: [DependencyModule, StandalonePipe],
})
class StandaloneComponent {
@Input() public readonly name: string | null = null;
}

It imports DependencyModule, which provides DependencyComponent, and StandalonePipe, and ideally both should be mocked.

The answer is:

beforeEach(() => {
return MockBuilder(StandaloneComponent);
});

Under the hood it marks StandaloneComponent as kept and sets shallow and export flags:

beforeEach(() => {
return MockBuilder().keep(StandaloneComponent, {
shallow: true,
export: true,
});
});

That's it. Now all imports of StandaloneComponent are mocks, and its properties, methods, injections and template are available for testing.

If you need to keep an import, simply call .keep with it. For example, if we wanted to keep StandalonePipe, the code would look like this:

beforeEach(() => {
return MockBuilder(StandaloneComponent).keep(StandalonePipe);
});

Live example

https://github.com/help-me-mom/ng-mocks/tree/main/examples/TestStandaloneComponent/test.spec.ts
import {
Component,
Input,
NgModule,
Pipe,
PipeTransform,
} from '@angular/core';

import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';

// A simple standalone pipe we are going to mock.
@Pipe({
name: 'standalone',
standalone: true,
})
class StandalonePipe implements PipeTransform {
transform(value: string | null): string {
return `${value}:${this.constructor.name}`;
}
}

// A simple dependency component we are going to mock.
@Component({
selector: 'dependency-standalone-component',
template: '<ng-content></ng-content>',
})
class DependencyComponent {
@Input() public readonly name: string | null = null;
}

// A module which declares and exports the dependency component.
@NgModule({
declarations: [DependencyComponent],
exports: [DependencyComponent],
})
class DependencyModule {}

// A standalone component we are going to test.
@Component({
selector: 'standalone',
template: `<dependency-standalone-component [name]="name">{{
name | standalone
}}</dependency-standalone-component>`,
standalone: true,
imports: [
DependencyModule,
StandalonePipe,
],
})
class StandaloneComponent {
@Input() public readonly name: string | null = null;
}

describe('TestStandaloneComponent', () => {
beforeEach(() => {
return MockBuilder(StandaloneComponent);
});

it('renders dependencies', () => {
const fixture = MockRender(StandaloneComponent, {
name: 'test',
});

// asserting that we passed the input
const dependencyComponent = ngMocks.findInstance(
DependencyComponent,
);
expect(dependencyComponent.name).toEqual('test');

// asserting how we called the pipe
const standalonePipe = ngMocks.findInstance(StandalonePipe);
// it's possible because of autoSpy.
expect(standalonePipe.transform).toHaveBeenCalledWith('test');

// or asserting virtual DOM
expect(
ngMocks.input(
ngMocks.find(fixture, DependencyComponent),
'name',
),
).toEqual('test');
});
});