Mocking in Angular Tests – Why It Hurts, and How ng-mocks Helps
Anyone who has written tests — not just in Angular — knows that mocking the dependencies of the code under test can at times be a real nuisance. How painful it gets depends largely on the quality and complexity of the codebase. Consider the following example:
TestBed.configureTestingModule({
declarations: [
// The only declaration we care about.
AppBaseComponent,
// Dependencies.
AppHeaderComponent,
AppDarkDirective,
TranslatePipe,
// ...
],
imports: [
CommonModule,
AppSearchModule,
// ...
],
providers: [
SearchService,
// ...
],
});
When the dependency tree grows deep, the sheer volume of mocks can seriously slow us down and keep us from actually writing test logic. Wouldn't it be nice to simplify and shorten the test module setup instead?
Enter ng-mocks, a library designed precisely to ease this pain. It supports Angular from version 5 upward (and works with Ivy in recent releases), and it plays nicely with both jest and jasmine — a reassuring level of compatibility. But is adding yet another dependency to your project worth it? Below, I will try to give you the information you need to decide.
A note on the code examples
All snippets in this article are written following the SIFERS (Simple Injectable Functions Explicitly Returning State) pattern. If you're not familiar with that approach, I recommend reading Moshe Kolodny's excellent article on the topic.
The helper-based approach
In its most basic form, ng-mocks ships with a collection of utility functions that make Angular's TestBed easier to use. On the surface, TestBed doesn't look complicated, but as the dependency graph grows, the amount of mocking ceremony grows with it. The longer the chain, the more likely you'll end up staring at errors like NullInjectorError: No provider for XXX — and the more time you'll lose to fixing them instead of writing tests.
Let's take a look at what the library gives us:
MockComponent
This function creates a mock version of a component of the given type. It preserves the original public interface — Inputs, Outputs, selector, transclusion support, and more — but its implementation is empty.
MockModule
Similarly, this creates a mock of the given module. It mirrors the module's public surface but has no real implementation. Even better, it also mocks all the intermediate dependencies the module imports — so in most cases you don't have to handle them yourself at all.
MockProvider
This utility allows you to mock a provider, whether it's a service or an InjectionToken. It offers various ways to define providers in the same style you're used to from standard Angular.
MockService, MockDirective and MockPipe
These functions let you mock elements matching their names. You'll find more detailed examples in the official documentation.
The purpose of these helpers is fairly intuitive. Let's look at the example below — note that no ng-mocks is used yet. The source code for everything in this article is available on my GitHub (link at the end).
await TestBed
.configureTestingModule({
declarations: [
WeatherWidgetComponent,
SpeedUnitPipe,
TemperatureUnitPipe
],
providers: [
{ provide: Environment, useValue: {} },
{ provide: WeatherService, useValue: { fetchCurrent: () => Promise.resolve(sampleWeather) }}
]
})
.overridePipe(SpeedUnitPipe, {})
.overridePipe(TemperatureUnitPipe, {})
.compileComponents();
Now let's apply those helpers to the same example:
await TestBed
.configureTestingModule({
declarations: [
WeatherWidgetComponent,
MockPipe(SpeedUnitPipe),
MockPipe(TemperatureUnitPipe)
],
providers: [
MockProvider(Environment),
MockProvider(WeatherService, { fetchCurrent: () => Promise.resolve(sampleWeather) })
]
})
.compileComponents();
The most obvious improvement is a much cleaner syntax. Is that an added value? In very small projects, the difference might be negligible. But as the codebase grows, readability and clarity become a serious advantage — especially when you come back to tests months later, or when you work with someone else's test code. But that's not all ng-mocks can do.
Using MockBuilder
To quote the library's documentation, in free translation: "MockBuilder is the easiest way to mock virtually anything." Let's see what that means in practice.
MockBuilder
MockBuilder is a function for mocking various parts of an app, built as a fluent API — a chain of well-named methods, in the spirit Uncle Bob would approve of. It takes up to two optional arguments. The first one identifies the thing that should not be mocked (a component or an InjectionToken), and the second one tells it which module to mock, along with everything it depends on. You can use either argument on its own, both together, or none at all.
What's nice is that it produces a compact and readable block of code that largely covers the whole environment setup, so you can move straight on to writing test cases. But let the code speak for itself — this is what the previous section would look like with MockBuilder:
await MockBuilder(WeatherWidgetComponent, WeatherModule)
.mock(WeatherService, {
fetchCurrent: () => Promise.resolve(sampleWeather)
});
As you can see, MockBuilder does much of the heavy lifting. The API is well described in the official docs, so I won't duplicate that info here. And even if you get stuck, you can always fall back to the classic TestBed approach:
await MockBuilder(WeatherWidgetComponent, WeatherModule)
.mock(WeatherService, {
fetchCurrent: () => Promise.resolve(sampleWeather)
})
.beforeCompileComponents(testBed => { /* ... */ });
MockRender
This function is used for rendering components. Internally, it calls Angular's TestBed.createComponent, but it returns a different type: MockedComponentFixture<Component>. For the curious, the library's authors explain the reasoning here.
MockInstance
This helper simplifies preparing and tweaking class instances — especially handy when you're writing spies. Here's a quick example:
MockInstance(SampleService, 'fetch', () => { /* ... */ })
Notice that the last argument makes it easy to set up a spy:
MockInstance(SampleService, 'fetch', () =>
jest.fn().mockImplementation(() => { /* ... */ })
)
So it's yet another tool in the toolbox, and quite useful once you get to know it. For further details, refer to the documentation.
ngMocks
The ngMocks namespace collects a bunch of helpers for a variety of scenarios. To keep this article from turning into a book, I'll just point you to the official documentation.
The upside
Let's sum it up. The library gives you a set of utilities that streamline the traditional TestBed-based workflow. Beyond that, there's MockBuilder, which greatly expands what's possible and limits your direct interaction with TestBed to the rare edge cases. The result: less setup code, mostly automated mock creation, and more time to write actual assertions. And that's what really counts.
The answer, unless your project is really tiny, is straightforward in my view. It's well worth using.
Summary
All the code examples above came from a demo app created for this article. The whole project can be downloaded from my GitHub repo. Here I'll just present the body of the setup() function — the individual test cases would be identical in each scenario anyway.
Without ng-mocks
Component
const sampleWeather = [
{ timePoint: 1 }
];
await TestBed
.configureTestingModule({
declarations: [
WeatherWidgetComponent,
SpeedUnitPipe,
TemperatureUnitPipe
],
providers: [
{ provide: Environment, useValue: {} },
{ provide: WeatherService, useValue: {fetchCurrent: () => Promise.resolve(sampleWeather)} }
]
})
.overridePipe(SpeedUnitPipe, {})
.overridePipe(TemperatureUnitPipe, {})
.compileComponents();
const fixture = TestBed.createComponent(WeatherWidgetComponent);
const component = fixture.componentInstance;
return { sampleWeather, fixture, component };
Pipe
TestBed
.configureTestingModule({
providers: [
{ provide: Environment, useValue: customEnvironment }
]
});
const pipe = new SpeedUnitPipe(
TestBed.inject(Environment)
);
return { pipe };
Service
const environment: Environment = { apiUrl: 'SAMPLE', system: null };
TestBed
.configureTestingModule({
providers: [
{ provide: HttpClient, useValue: {get: (...args) => of(null)} },
{ provide: Environment, useValue: environment },
WeatherService,
]
});
const httpClient = TestBed.inject(HttpClient);
const service = TestBed.inject(WeatherService);
const httpGet: jest.SpyInstance = jest.spyOn(httpClient, 'get');
return { environment, httpClient, service, httpGet };
ng-mocks – only helpers
Component
const sampleWeather = [
{ timePoint: 1 }
];
await TestBed
.configureTestingModule({
declarations: [
WeatherWidgetComponent,
MockPipe(SpeedUnitPipe),
MockPipe(TemperatureUnitPipe)
],
providers: [
MockProvider(Environment),
MockProvider(WeatherService, { fetchCurrent: () => Promise.resolve(sampleWeather) })
]
})
.compileComponents();
const fixture = MockRender(WeatherWidgetComponent);
const component = fixture.point.componentInstance;
return { sampleWeather, fixture, component };
Pipe
TestBed.configureTestingModule({
providers: [ MockProvider(Environment, customEnvironment) ]
});
const pipe = new SpeedUnitPipe(
TestBed.inject(Environment)
);
return { pipe };
Service
const environment: Environment = { apiUrl: 'SAMPLE', system: null };
TestBed
.configureTestingModule({
providers: [
MockProvider(HttpClient),
MockProvider(Environment, environment),
WeatherService
]
});
const httpClient = TestBed.inject(HttpClient);
const service = TestBed.inject(WeatherService);
const httpGet: jest.SpyInstance = jest.spyOn(httpClient, 'get');
return { environment, httpClient, service, httpGet };
ng-mocks using MockBuilder
Component
const sampleWeather = [
{ timePoint: 1 }
];
await MockBuilder(WeatherWidgetComponent, WeatherModule)
.mock(WeatherService, {
fetchCurrent: () => Promise.resolve(sampleWeather)
});
const fixture = MockRender(WeatherWidgetComponent, { current$: new BehaviorSubject(sampleWeather) });
const component = fixture.point.componentInstance;
return { sampleWeather, fixture, component };
Pipe
const testingModule = MockBuilder()
.mock(Environment, customEnvironment)
.build();
TestBed.configureTestingModule(testingModule);
const pipe = new SpeedUnitPipe(
TestBed.inject(Environment)
);
return { pipe };
Service
const environment = { apiUrl: 'SAMPLE', system: null };
const testingModule = MockBuilder(WeatherService, WeatherModule)
.mock(Environment, environment)
.build();
TestBed.configureTestingModule(testingModule);
const httpClient = TestBed.inject(HttpClient);
const service = TestBed.inject(WeatherService);
const httpGet: jest.SpyInstance = jest.spyOn(httpClient, 'get');
return { environment, httpClient, service, httpGet };
Now it's your turn. If you haven't tried the library yet, I highly encourage you to download it and see for yourself. Happy coding!
