What is ng-mocks?
ng-mocks is a powerful library for Angular that simplifies the process of creating mock components, directives, pipes, and services for unit testing. It helps developers to isolate the unit of work and test it without dependencies on other parts of the application.
What are ng-mocks's main functionalities?
Mock Components
This feature allows you to create mock versions of Angular components. This is useful for isolating the component under test from its child components.
import { MockComponent } from 'ng-mocks';
import { MyComponent } from './my-component';
TestBed.configureTestingModule({
declarations: [MockComponent(MyComponent)]
});
Mock Directives
This feature allows you to create mock versions of Angular directives. This is useful for isolating the component under test from its directives.
import { MockDirective } from 'ng-mocks';
import { MyDirective } from './my-directive';
TestBed.configureTestingModule({
declarations: [MockDirective(MyDirective)]
});
Mock Pipes
This feature allows you to create mock versions of Angular pipes. This is useful for isolating the component under test from its pipes.
import { MockPipe } from 'ng-mocks';
import { MyPipe } from './my-pipe';
TestBed.configureTestingModule({
declarations: [MockPipe(MyPipe)]
});
Mock Services
This feature allows you to create mock versions of Angular services. This is useful for isolating the component under test from its services.
import { MockProvider } from 'ng-mocks';
import { MyService } from './my-service';
TestBed.configureTestingModule({
providers: [MockProvider(MyService)]
});
Other packages similar to ng-mocks
jest
Jest is a delightful JavaScript testing framework with a focus on simplicity. It provides a powerful mocking library that can be used to mock functions, modules, and timers. While Jest is not specific to Angular, it can be used in conjunction with Angular to achieve similar mocking capabilities.
sinon
Sinon is a standalone test spies, stubs, and mocks library for JavaScript. It works with any unit testing framework and provides powerful mocking capabilities. Like Jest, Sinon is not specific to Angular but can be used to mock dependencies in Angular applications.
ts-mockito
ts-mockito is a mocking library for TypeScript inspired by the Java library Mockito. It provides a simple API for creating mock objects and verifying interactions. ts-mockito is not specific to Angular but can be used to mock dependencies in Angular applications.
ngMocks
Helper function for creating angular mocks for test.
Why use this?
Sure, you could flip a flag on schema errors to make your component dependencies not matter. Or you could use this to mock them out and have the ability to assert on their inputs or emit on an output to assert on a side effect.
MockComponent(s)
- Mocked component with the same selector
- Inputs and Outputs with alias support
- Each component instance has its own EventEmitter instances for outputs
- Mocked component templates are ng-content tags to allow transclusion
- Allows ng-model binding (You will have to add FormsModule to TestBed imports)
- Mocks Reactive Forms (You will have to add ReactiveFormsModule to TestBed imports)
- __simulateChange - calls
onChanged
on the mocked component bound to a FormControl - __simulateTouch - calls
onToched
on the mocked component bound to a FormControl
- exportAs support
Usage Example
import { async, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MockComponent } from 'ng-mocks';
import { DependencyComponent } from './dependency.component';
import { TestedComponent } from './tested.component';
describe('TestedComponent', () => {
let fixture: ComponentFixture<TestedComponent>;
beforeEach(async() => {
TestBed.configureTestingModule({
declarations: [
TestedComponent,
MockComponent(DependencyComponent)
]
})
.compileComponents();
.then(() => {
fixture = TestBed.createComponent(TestedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('should send the correct value to the dependency component input', () => {
const mockedComponent = fixture.debugElement
.query(By.css('dependency-component-selector'))
.componentInstance as DependencyComponent;
expect(mockedComponent.someInput).toEqual('foo'); if you casted mockedComponent as the original component type then this is type safe
});
it('should do something when the dependency component emits on its output', () => {
const mockedComponent = fixture.debugElement
.query(By.directive(MockComponent(DependencyComponent)))
.componentInstance as DependencyComponent;
mockedComponent.someOutput.emit(new Foo()); if you casted mockedComponent as the original component type then this is type safe
fixture.detectChanges();
});
});
MockDirective(s)
- Mocked directive with the same selector
- Inputs and Outputs with alias support
- Each directive instance has its own EventEmitter instances for outputs
- exportAs support
Usage Example
import { async, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MockComponent } from 'ng-mocks';
import { DependencyDirective } from './dependency.directive';
import { TestedComponent } from './tested.component';
describe('TestedComponent', () => {
let fixture: ComponentFixture<TestedComponent>;
beforeEach(async() => {
TestBed.configureTestingModule({
declarations: [
TestedComponent,
MockDirective(DependencyDirective)
]
})
.compileComponents();
.then(() => {
fixture = TestBed.createComponent(TestedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('should send the correct value to the dependency component input', () => {
const debugElement = fixture.debugElement.query(By.directive(MockDirective(DependencyDirective)));
const mockedDirectiveInstance = debugElement.injector
.get(MockDirective(DependencyDirective)) as DependencyDirective;
expect(mockedDirectiveInstance.someInput).toEqual('foo');
});
it('should do something when the dependency directive emits on its output', () => {
const debugElement = fixture.debugElement.query(By.directive(MockDirective(DependencyDirective)));
const mockedDirectiveInstance = debugElement.injector
.get(MockDirective(DependencyDirective)) as DependencyDirective;
mockedDirectiveInstance.someOutput.emit(new Foo()); if you casted mockedDirective as the original component type then this is type safe
fixture.detectChanges();
});
});
MockPipe(s)
- Mocked pipe with the same name.
- Ability to override the transform function with a type-safe function
- Default transform is () => undefined to prevent problems with chaining
Personally, I found the best thing to do for assertions is to override the transform to write the args so that I can assert on the arguments.
Usage Example
import { async, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MockComponent } from 'ng-mocks';
import { DependencyPipe } from './dependency.pipe';
import { TestedComponent } from './tested.component';
describe('TestedComponent', () => {
let fixture: ComponentFixture<TestedComponent>;
beforeEach(async() => {
TestBed.configureTestingModule({
declarations: [
TestedComponent,
MockPipe(DependencyPipe, (...args) => JSON.stringify(args)),
]
})
.compileComponents();
.then(() => {
fixture = TestBed.createComponent(TestedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
describe('with transform override', () => {
it('should return the result of the provided transform function', () => {
expect(fixture.debugElement.query(By.css('#elementUsingPipe')).nativeElement.innerHTML).toEqual('foo');
});
});
});
Mocked Reactive Forms Components
- Set value on the formControl by calling __simulateChange
- Set touched on the formControl by calling __simulateTouch
- Use the
MockedComponent
type to stay typesafe: MockedComponent<YourReactiveFormComponent>
Usage Example
import { async, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MockComponent, MockedComponent } from 'ng-mocks';
import { ReactiveFormComponent } from './dependency.component';
import { TestedComponent } from './tested.component';
describe('TestedComponent', () => {
let fixture: ComponentFixture<TestedComponent>;
beforeEach(async() => {
TestBed.configureTestingModule({
declarations: [
TestedComponent,
MockComponent(DependencyComponent)
]
})
.compileComponents();
.then(() => {
fixture = TestBed.createComponent(TestedComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}));
it('should send the correct value to the dependency component input', () => {
const mockedReactiveFormComponent = fixture.debugElement
.query(By.css('dependency-component-selector'))
.componentInstance as MockedComponent<ReactiveFormComponent>;
mockedReactiveFormComponent.__simulateChange('foo');
expect(component.formControl.value).toBe('foo')
});
});
MockDeclaration(s)
It figures out if it is a component, directive, or pipe and mocks it for you
MockModule
- Mocks all components, directives, and pipes using MockDeclaration
- Providers are all mocked as empty objects
- Module Dependencies are also mocked
For providers I typically will use TestBed.get(SomeProvider) and extend it using a library like ts-mocks.
Usage Example
describe('MockModule', () => {
let fixture: ComponentFixture<ComponentSubject>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
ComponentSubject
],
imports: [
MockModule(DependencyModule)
],
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(ComponentSubject);
fixture.detectChanges();
});
}));
});
Find an issue or have a request?
Report it as an issue or submit at PR. I'm open to contributions.
https://github.com/ike18t/ng-mocks