Introduction
Services that interact with external APIs, like Contentful, play a key role in many Angular applications. However, when it comes to writing unit tests, these same services can pose significant challenges.
Common issues with real calls:
- Fragility: Tests can fail if there are issues with the network or the external service.
- Costs: Making real calls can consume resources from a production account.
Mocking services is a powerful technique for streamlining testing and improving reliability. By simulating the behavior of external dependencies, such as APIs, it becomes easier to focus on the application’s logic without relying on live data or network conditions. This guide demonstrates how to set up a service with mocking, showcasing its implementation and how it can drastically reduce real API calls to a service like Contentful during testing. The result is faster, more efficient test runs and greater control over test scenarios.
How to Set Up an API service
Before diving into mocking, it’s essential to understand how the real service is typically implemented. This service will interact with the Contentful API to fetch entries of different content types.
A Basic ContentfulService
Example
import { Injectable } from '@angular/core';
import { createClient, ContentfulClientApi } from 'contentful';
import { Page } from './model';
@Injectable({
providedIn: 'root',
})
export class ContentfulService {
private client: ContentfulClientApi;
constructor() {
this.client = createClient({
space: 'YOUR_SPACE_ID',
accessToken: 'YOUR_ACCESS_TOKEN',
host: 'YOUR_HOST'
});
}
// Method to get a type of content from Contentful
getPages(): Promise<Page[]>
const response = this.client.getEntries({ content_type: 'page' });
return response.items.map((item) => ({
title: item.fields.title,
content: item.fields.content,
// Map the rest of the items
})
}
}
Key Elements in ContentfulService
-
createClient
: Initializes the Contentful client using yourspace
ID,accessToken
and host. These are typically stored in environment variables for security. -
getPages
: A method that retrieves entries from Contentful and map them into the type you are going to use in your app.
The Problem
Before implementing the mock solution, tests directly depended on the real ContentfulService
, leading to:
- Lots of calls during a complete test suite, making tests dependent on real data and the service’s state (More than 170 calls in my case).
- Inconsistent results due to network issues or changes in the service data.
- Unnecessary usage of resources from the service account.
The Solution: Mocking ContentfulService
The key to solving this problem is replacing the real service with a mock version in the tests, avoiding any external calls. Here’s the code that implements this solution:
Creating a Mock Provider
import { ContentfulService } from './contentful.service';
export const provideContentfulTesting = () => ({
provide: ContentfulService,
useValue: {
getPages: jasmine.createSpy('getPages').and.returnValue(Promise.resolve([])),
},
});
This mock:
- Replaces the real function (getPages) with a Jasmine spy.
- Returns default values (in this case, an empty list) to ensure the tests don’t rely on external data.
Using the Mock in the Test Module
import { provideContentfulTesting } from '@shared/services/contentful/contentful-testing';
describe('ImportDialogComponent', () => {
beforeEach(async() => {
await TestBed.configureTestingModule({
imports: [yourModules],
declarations: [yourImplementedComponents],
providers: [
otherProviders,
provideContentfulTesting(),
],
}).compileComponents();
});
// Rest of your code
});
By including provideContentfulTesting()
in the providers
array, Angular uses the mock instead of the real service for all tests in that module.
Benefits
1. Increased Consistency
Tests no longer depend on changing data in Contentful or the service’s availability, ensuring reliable results.
2. Easier Edge Case Testing
Using spies allows you to control return data and test specific scenarios (e.g., handling errors from getEntries
).
contentfulMock.getEntries.and.returnValue(Promise.reject(new Error('API Error')));
3. Reduced Costs and Load on External APIs
No unnecessary resources are consumed from the Contentful account. Remember that Contentful have a cost, and you don’t want to exceed the limit of your account. In some projects, I have seen that the amount of calls made by tests reached 400K calls in a month, so be careful with these things.
Conclusion
For services like Contentful, you should always have a service that manages the API calls, and mocking it in unit tests something essential to avoid an extra cost in your account.
Top comments (0)