How to mock providers in Angular tests
A mock provider in Angular tests can be created by MockProvider
function.
The function supports services and tokens.
Also, it accepts a shape of its service, in order to provide own properties,
and values for tokens, otherwise the token's value will be undefined
.
TestBed.configureTestingModule({
providers: [
// All methods are empty dummies.
MockProvider(Service),
// All methods are empty dummies,
// but get$() returns EMPTY.
MockProvider(Service, {
get$: () => EMPTY,
}),
// provides undefined
MockProvider(TOKEN),
// provides 'value'
MockProvider(TOKEN, 'value'),
],
});
useValue
TestBed.configureTestingModule({
providers: [
MockProvider(Service, {name: 'mock'}, 'useValue'),
// generates
// {
// provide: Service,
// useValue: {name: 'mock},
// }
MockProvider(TOKEN, 'token', 'useValue', true),
// generates
// {
// provide: TOKEN,
// useValue: 'token',
// multi: true,
// }
],
});
useExisting
TestBed.configureTestingModule({
providers: [
MockProvider(Service, MockService, 'useExisting'),
// generates
// {
// provide: Service,
// useExisting: MockService,
// }
MockProvider(TOKEN, FAKE, 'useExisting', true),
// generates
// {
// provide: TOKEN,
// useExisting: FAKE,
// multi: true,
// }
],
});
useClass
TestBed.configureTestingModule({
providers: [
MockProvider(Service, MockService, 'useClass'),
// generates
// {
// provide: Service,
// useClass: MockService,
// }
MockProvider(Service, MockService, 'useClass', true),
// generates
// {
// provide: Service,
// useClass: MockService,
// multi: true,
// }
MockProvider(Service, MockService, 'useClass', [DbService]),
// generates
// {
// provide: Service,
// useClass: MockService,
// deps: [DbService],
// }
MockProvider(Service, MockService, 'useClass', {
multi: true,
deps: [DbService],
}),
// generates
// {
// provide: Service,
// useClass: MockService,
// deps: [DbService],
// multi: true,
// }
],
});
useFactory
TestBed.configureTestingModule({
providers: [
MockProvider(Service, () => new MockService(), 'useFactory'),
// generates
// {
// provide: Service,
// useFactory: () => new MockService(),
// }
MockProvider(Service, () => new MockService(), 'useFactory', true),
// generates
// {
// provide: Service,
// useFactory: () => new MockService(),
// multi: true,
// }
MockProvider(Service, () => new MockService(), 'useFactory', [DbService]),
// generates
// {
// provide: Service,
// useFactory: (db) => new MockService(db),
// deps: [DbService],
// }
MockProvider(Service, MockService, 'useFactory', {
multi: true,
deps: [DbService],
}),
// generates
// {
// provide: Service,
// useFactory: (db) => new MockService(db),
// deps: [DbService],
// multi: true,
// }
],
});
Simple example
Let's pretend that in an Angular application TargetComponent
depends on DependencyService
service,
and, in sake of avoiding overhead, its mock object should be used.
Usually, a test looks like:
describe('Test', () => {
let component: TargetComponent;
let fixture: ComponentFixture<TargetComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TargetComponent],
providers: [
// Annoying dependencies.
DependencyService,
ObservableService,
],
});
fixture = TestBed.createComponent(TargetComponent);
component = fixture.componentInstance;
});
});
To replace these services with their mock providers, simply pass their classes into MockProvider
:
TestBed.configureTestingModule({
declarations: [TargetComponent],
providers: [
// profit
MockProvider(DependencyService),
MockProvider(ObservableService, {
prop$: EMPTY,
getStream$: () => EMPTY,
}),
],
});
Or, to be like a pro, use MockBuilder
, .mock
method
and call MockRender
:
describe('Test', () => {
beforeEach(() => {
// DependencyService is a provider in ItsModule.
return MockBuilder(TargetComponent, ItsModule)
// ObservableService is a provider in ItsModule, which we need to customize
.mock(ObservableService, {
prop$: EMPTY,
getStream$: () => EMPTY,
});
});
it('should create', () => {
const fixture = MockRender(TargetComponent);
expect(fixture.point.componentInstance).toBeDefined();
});
});
The most common error developers meet, when they create mock services, is "TypeError: Cannot read property 'subscribe' of undefined".
If we are encountering it too, please read a section called How to fix TypeError: Cannot read property 'subscribe' of undefined.
Advanced example
An Advanced example of mocking providers in Angular tests. Please, pay attention to comments in the code.
describe('MockProvider', () => {
const mockObj = { value: 123 };
beforeEach(() =>
TestBed.configureTestingModule({
declarations: [TargetComponent],
imports: [CommonModule],
providers: [
MockProvider(Dependency1Service),
MockProvider(Dependency2Service, { name: 'd2:mock' }),
MockProvider(UNK_TOKEN, 'mock token'),
MockProvider(STR_TOKEN, 'mock'),
MockProvider(OBJ_TOKEN, mockObj),
MockProvider('pri', 'pri'),
],
}).compileComponents(),
);
it('uses mock providers', () => {
// overriding the token's data that does affect the provided token.
mockObj.value = 321;
const fixture = MockRender(TargetComponent);
expect(
fixture.point.injector.get(Dependency1Service).echo(),
).toBeUndefined();
expect(
fixture.point.injector.get(Dependency2Service).echo(),
).toBeUndefined();
expect(fixture.point.injector.get(OBJ_TOKEN)).toBe(
mockObj as any,
);
expect(fixture.nativeElement.innerHTML).not.toContain('"target"');
expect(fixture.nativeElement.innerHTML).toContain('"d2:mock"');
expect(fixture.nativeElement.innerHTML).toContain('"mock token"');
expect(fixture.nativeElement.innerHTML).toContain('"mock"');
expect(fixture.nativeElement.innerHTML).toContain('"value": 321');
expect(fixture.nativeElement.innerHTML).toContain('"pri"');
});
});