How to mock directives in Angular tests
A mock directive in Angular tests can be created by MockDirective function.
The mock directive has the same interface as its original directive,
but all its methods are dummies.
In order to create a mock directive, pass the original directive into MockDirective function.
TestBed.configureTestingModule({
declarations: [
// for a single directive
MockDirective(Directive),
// for a set of directives
...MockDirectives(Directive1, Directive2),
],
});
A mock directive has:
- support for attribute and structural directives
- the same
selector - the same
InputsandOutputswith alias support - support for
@ContentChildand@ContentChildren - support for
ControlValueAccessor,ValidatorandAsyncValidator - supports
exportAs
tip
Information about mocking FormControl, ControlValueAccessor, Validator and AsyncValidator is in a different section.
Simple example
Let's assume that an Angular application has TargetComponent that depends on DependencyDirective directive,
and we would like to use its mock object in order to facilitate unit tests.
Usually a test looks like:
describe('Test', () => {
let component: TargetComponent;
let fixture: ComponentFixture<TargetComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
// our component for testing
TargetComponent,
// the annoying dependency
DependencyDirective,
],
});
fixture = TestBed.createComponent(TargetComponent);
component = fixture.componentInstance;
});
});
To turn a directive into a mock directive, simply pass its original class into MockDirective:
TestBed.configureTestingModule({
declarations: [
TargetComponent,
// profit
MockDirective(DependencyDirective),
],
});
Or be like a pro and use MockBuilder, its .mock method
and MockRender:
describe('Test', () => {
beforeEach(() => {
return MockBuilder(TargetComponent).mock(DependencyDirective);
});
it('should create', () => {
const fixture = MockRender(TargetComponent);
expect(fixture.point.componentInstance).toBeDefined();
});
});
Advanced example with attribute directives
An advanced example about mocking attribute directives. Please, pay attention to comments in the code.
describe('MockDirective:Attribute', () => {
beforeEach(() => {
return MockBuilder(TestedComponent).mock(DependencyDirective);
});
it('sends the correct value to the input', () => {
const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;
// The same as
// fixture.debugElement.query(
// By.css('span')
// ).injector.get(DependencyDirective)
// but easier and more precise.
const mockDirective = ngMocks.get(
ngMocks.find('span'),
DependencyDirective,
);
// Let's pretend DependencyDirective has 'someInput'
// as an input. TestedComponent sets its value via
// `[someInput]="value"`. The input's value will be passed into
// the mock directive so we can assert on it.
component.value = 'foo';
fixture.detectChanges();
// Thanks to ng-mocks, this is type safe.
expect(mockDirective.someInput).toEqual('foo');
});
it('does something on an emit of the child directive', () => {
const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;
// The same as
// fixture.debugElement.query(
// By.css('span')
// ).injector.get(DependencyDirective)
// but easier and more precise.
const mockDirective = ngMocks.get(
ngMocks.find('span'),
DependencyDirective,
);
// Again, let's pretend DependencyDirective has an output called
// 'someOutput'. TestedComponent listens on the output via
// `(someOutput)="trigger()"`.
// Let's install a spy and trigger the output.
ngMocks.stubMember(
component,
'trigger',
jasmine.createSpy(), // or jest.fn()
);
mockDirective.someOutput.emit();
// Assert on the effect.
expect(component.trigger).toHaveBeenCalled();
});
});
Advanced example with structural directives
An advanced example of mocking structural directives in Angular tests. Please, pay attention to comments in the code.
important
It is important to render a structural directive with the right context first, if we want to assert on its nested elements.
describe('MockDirective:Structural', () => {
// IMPORTANT: by default structural directives are not rendered.
// Because they might require a context which should be provided.
// Usually a developer knows the context and can render it
// manually with proper setup.
beforeEach(() => {
return MockBuilder(TargetComponent, TargetModule).mock(
DependencyDirective,
{
// render: true, // <-- a flag to render the directive by default
},
);
});
it('renders content of the child structural directive', () => {
const fixture = MockRender(TargetComponent);
// Let's assert that nothing has been rendered inside
// the structural directive by default.
expect(fixture.nativeElement.innerHTML).not.toContain('>content<');
// And let's render it manually now.
const mockDirective = ngMocks.findInstance(DependencyDirective);
ngMocks.render(mockDirective, mockDirective);
// The content of the structural directive should be rendered.
expect(fixture.nativeElement.innerHTML).toContain('>content<');
});
});