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
@Inputsand@Outputswith alias support - templates with pure
<ng-content>tags to allow transclusion - support for
@ContentChildand@ContentChildren - support for
ControlValueAccessor,ValidatorandAsyncValidator - support for
exportAs
tip
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(() => {
return MockBuilder(TargetComponent).mock(DependencyComponent);
});
it('should create', () => {
const fixture = MockRender(TargetComponent);
expect(fixture.point.componentInstance).toBeDefined();
});
});
Advanced example
An advanced example about mocking components. Please, pay attention to comments in the code.
describe('MockComponent', () => {
beforeEach(() => {
return MockBuilder(TestedComponent).mock(DependencyComponent);
});
it('sends the correct value to the child input', () => {
const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;
// The same as
// fixture.debugElement.query(
// By.css('app-child')
// ).componentInstance
// but properly typed.
const mockComponent =
ngMocks.find<DependencyComponent>(
'app-child',
).componentInstance;
// Let's pretend that DependencyComponent has 'someInput' as
// an input. TestedComponent 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(TestedComponent);
const component = fixture.point.componentInstance;
// The same as
// fixture.debugElement.query(
// By.directive(DependencyComponent)
// ).componentInstance
// but properly typed.
const mockComponent = ngMocks.findInstance(DependencyComponent);
// Again, let's pretend DependencyComponent has an output
// called 'someOutput'. TestedComponent 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<DependencyComponent>(`
<app-child>
<p>inside content</p>
</app-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<DependencyComponent>(`
<app-child>
<ng-template #something>
<p>inside template</p>
</ng-template>
<p>inside content</p>
</app-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(DependencyComponent)
.nativeElement.innerHTML;
expect(mockNgTemplate).toContain('<p>inside template</p>');
});
});