DEV Community

Fran Saoco
Fran Saoco

Posted on • Edited on

3 1 1 2 1

Testing Angular HTTP Services using Jest

The tests check if the HTTP service works correctly by:

  1. Making fake API calls (GET/POST)
  2. Returning test data
  3. Handling errors properly
  4. All without using real internet connections

This ensures the service works as expected before using it in real apps.

data-http-service.ts

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

const API_URL = 'https://api.example.com/data';

@Injectable({
  providedIn: 'root'
})
export class DataHttpService {
  http = inject(HttpClient);

  getData(): Observable<unknown> {
    return this.http.get<unknown>(API_URL);
  }

  postData(payload: unknown): Observable<unknown> {
    return this.http.post<unknown>(API_URL, payload);
  }

}
Enter fullscreen mode Exit fullscreen mode

Production tip: Store API URLs in environment variables (never in code) and mock them for testing, complete walkthrough: Using and Mocking Angular Environments with Jest

data-http-service.spec.ts

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { DataHttpService } from './data-http.service';

describe('DataHttpService', () => {
  let service: DataHttpService;
  let httpMock: HttpTestingController;
  const API_URL = 'https://api.example.com/data';
  const mockData = { id: 1, name: 'Test Data' };

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [DataHttpService]
    });

    service = TestBed.inject(DataHttpService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();  // Verify no outstanding requests
  });


  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  describe('.getData', () => {
    it('should make GET request and return data', () => {
      service.getData().subscribe(data => {
        expect(data).toEqual(mockData);
      });

      const req = httpMock.expectOne(API_URL);
      expect(req.request.method).toEqual('GET');
      req.flush(mockData);
    });

    it('should handle GET errors', () => {
      const mockError = { status: 404, statusText: 'Not Found' };

      service.getData().subscribe({
        next: () => fail('Should have failed'),
        error: (error) => {
          expect(error.status).toEqual(mockError.status);
        }
      });

      const req = httpMock.expectOne(API_URL);
      req.flush(null, mockError);
    });
  });

  describe('.postData', () => {
    it('should make POST request with payload', () => {
      const testPayload = { data: 'test' };

      service.postData(testPayload).subscribe(data => {
        expect(data).toEqual(mockData);
      });

      const req = httpMock.expectOne(API_URL);
      expect(req.request.method).toEqual('POST');
      expect(req.request.body).toEqual(testPayload);
      req.flush(mockData);
    });

    it('should handle POST errors', () => {
      const testPayload = { data: 'test' };
      const mockError = { status: 500, statusText: 'Server Error' };

      service.postData(testPayload).subscribe({
        next: () => fail('Should have failed'),
        error: (error) => {
          expect(error.status).toEqual(mockError.status);
        }
      });

      const req = httpMock.expectOne(API_URL);
      req.flush(null, mockError);
    });
  });

});
Enter fullscreen mode Exit fullscreen mode

Neon image

Build better on Postgres with AI-Assisted Development Practices

Compare top AI coding tools like Cursor and Windsurf with Neon's database integration. Generate synthetic data and manage databases with natural language.

Read more →

Top comments (2)

Collapse
 
damiansiredev profile image
Damian Sire (Dev)

I followed the series of articles and it's great! Just one comment, because many people come and copy things literally without thinking much xD (or without adapting them to their specific case).

When you copy this, the API_URL constant in this case is defined both in the service and in the test file. This works, but if the URL changes, you must remember to update it in both places.

So, when you apply this, you could export the constant from the service file (export const API_URL = ...;) and import it into the test file. This ensures you always use the same URL.

Also, keeping it separate in the tests can also be useful if you need to test different endpoints in the future within the same spec file (although in that case you could import more than one)

Remember to evaluate and think critically according to your case (You can even have a file with the API URLs).

To the author of the post, thank you for taking the time to make them and share :)

Collapse
 
fransaoco profile image
Fran Saoco • Edited

Thanks @damiansiredev !
You inspired a deep dive into Angular environment variables covering secure configuration, CI/CD integration, and proper mocking for unit tests:

Using and Mocking Angular Environments with Jest

Quickstart image

Django MongoDB Backend Quickstart! A Step-by-Step Tutorial

Get up and running with the new Django MongoDB Backend Python library! This tutorial covers creating a Django application, connecting it to MongoDB Atlas, performing CRUD operations, and configuring the Django admin for MongoDB.

Watch full video →

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay