Skip to main content

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. The real service constructor is never called. Because own properties only exist after construction, include their shape explicitly when they are needed.

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'),
],
});
class Service {
public readonly info = {
request: () => 'real',
};
}

ngMocks.autoSpy('jasmine');

TestBed.configureTestingModule({
providers: [
MockProvider(Service, {
info: MockService({
request: () => 'real',
}),
}),
],
});

const service = TestBed.inject(Service);

service.info.request();

expect(service.info.request).toHaveBeenCalledTimes(1);

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();
});
});
Please note

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.

https://github.com/help-me-mom/ng-mocks/blob/main/examples/MockProvider/test.spec.ts
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,
);
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"');
});
});