How to Avoid Unnecessary Calls in Your Tests — Angular

fndme

Gabriel Luis Freitas

Posted on November 26, 2024

How to Avoid Unnecessary Calls in Your Tests — Angular

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
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Elements in ContentfulService

  1. createClient: Initializes the Contentful client using your space ID, accessToken and host. These are typically stored in environment variables for security.
  2. 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([])),
  },
});
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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')));
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
fndme
Gabriel Luis Freitas

Posted on November 26, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related