DEV Community

Cover image for Preventing Real Service Calls in Tests: A Clean Approach with Angular
Gabriel Luis Freitas
Gabriel Luis Freitas

Posted on

Preventing Real Service Calls in Tests: A Clean Approach with Angular

The Problem

When writing tests for applications that interact with external services, we want to ensure that:

  1. No real external calls are made during testing
  2. We have predictable test data
  3. Tests run quickly without external dependencies
  4. We get clear error messages when something is misconfigured

The Solution

To fix these problems, we can follow this simple pattern:

  1. A utility function to prevent real service calls
  2. A testing provider that mocks the service
  3. Implementation in the actual service

1. The Error Prevention Utility

export const throwErrorIfTesting = (serviceName: string, testingProviderName: string): void => {  
  if (window.location.port === '9876') { // Karma's default test port  
    throw new Error(  
      `${serviceName} is not available in test mode, add ${testingProviderName} to the providers to fix the error`  
    );  
  }  
};
Enter fullscreen mode Exit fullscreen mode

This utility function detects when code is running in a test environment (by checking Karma’s default port) and throws a helpful error message if the real service is accidentally used.

2. The Testing Provider

export const provideApiTesting = () => ({  
  provide: ApiService,  
  useValue: {  
    getElements: jasmine.createSpy('getElements').and.returnValue(Promise.resolve([]))  
  }  
});
Enter fullscreen mode Exit fullscreen mode

This provider:

  • Creates a mock version of the service
  • Uses Jasmine spies for the method that calls the API
  • Returns predictable test data
  • Can be easily configured in individual tests

3. Implementation in the Service

@Injectable({  
  providedIn: 'root'  
})  
export class ApiService{  
  async getElements<T>() {  
    throwErrorIfTesting('ApiService', 'provideApiTesting');  
    // Real service implementation  
  }  
}
Enter fullscreen mode Exit fullscreen mode

The real service implements the error prevention check at the start of each method that would make external calls.

How to Mock responses

In Your Tests, you can easily mock the responses of this provider just by injecting the service and changing the returnValue of the Spy.

describe('YourComponent', () => {  
  let mockApiService: jasmine.SpyObj<ApiService>;  

  beforeEach(() => {  
    TestBed.configureTestingModule({  
      declarations: [YourComponent],  
      providers: [  
        provideApiTesting() // Use the testing provider  
      ]  
    });  
  });  

  beforeEach(() => {  
    fixture = TestBed.createComponent(YourComponent);  
    component = fixture.componentInstance;  
    fixture.detectChanges();  
  });  

  it('should load content', async () => {  
    const apiService= TestBed.inject(ApiService);  

    // Configure the mock response if needed  
    (apiService.getElementsas jasmine.Spy).and.returnValue(  
    Promise.resolve([/* your test data */])  
    );  

    // Run your test…  
  });  
});
Enter fullscreen mode Exit fullscreen mode

Benefits

  1. Fail-Fast Development: Issues are caught immediately with clear error messages
  2. Predictable Testing: No external dependencies or network calls
  3. Clear Separation: Test environment vs production code is clearly separated
  4. Type Safety: Testing providers maintain the same interface as the real service
  5. Easy to Mock: Simple to provide custom test data when needed
  6. Maintainable: Pattern is easy to understand and implement for new services

This pattern ensures that your tests remain reliable, fast, and maintainable while preventing accidental real service calls during testing.

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more