Skip to content
intermediate Phase 13 · Testing Angular Applications

Testing Services and Async Data

Verify service logic with doubles and controlled async timing.

40m
0 problems
Topic Progress 0%

Testing Services

Services are the most straightforward units to test. TestBed provides dependency injection for isolated testing.

Basic Service Test

import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';

describe('UserService', () => {
  let service: UserService;
  let httpMock: jasmine.SpyObj<HttpClient>;

  beforeEach(() => {
    // Create a spy object for HttpClient
    httpMock = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']);

    TestBed.configureTestingModule({
      providers: [
        UserService,
        { provide: HttpClient, useValue: httpMock },
      ],
    });

    service = TestBed.inject(UserService);
  });

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

  it('should fetch users', () => {
    const mockUsers: User[] = [
      { id: 1, name: 'Alice', email: 'alice@example.com' },
    ];

    httpMock.get.and.returnValue(of(mockUsers));

    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });

    expect(httpMock.get).toHaveBeenCalledWith('/api/users');
  });
});

Overriding Providers

TestBed.configureTestingModule({
  providers: [
    UserService,
    { provide: HttpClient, useValue: mockHttp },
    { provide: AuthService, useValue: mockAuth },
  ],
});

Jasmine Spies

it('should call the API', () => {
  const spy = spyOn(httpMock, 'get').and.returnValue(of([]));

  service.getUsers().subscribe();

  expect(spy).toHaveBeenCalledWith('/api/users');
});

Testing Async Service Code

Services often return Observables. Use fakeAsync and tick for deterministic async testing.

fakeAsync and tick

import { fakeAsync, tick, TestBed } from '@angular/core/testing';

describe('UserService with fakeAsync', () => {
  let service: UserService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [UserService],
    });
    service = TestBed.inject(UserService);
  });

  it('should fetch users after delay', fakeAsync(() => {
    let users: User[] = [];

    service.getUsers().subscribe(result => {
      users = result;
    });

    tick(1000); // Advance virtual time by 1000ms

    expect(users.length).toBe(2);
  }));
});

Testing with HttpTestingController

import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';

describe('UserService with HttpTestingController', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        UserService,
      ],
    });

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

  afterEach(() => {
    httpMock.verify();
  });

  it('should fetch users', () => {
    const mockUsers: User[] = [{ id: 1, name: 'Alice', email: 'alice@example.com' }];

    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });
});

Testing Error Handling

it('should handle errors', () => {
  service.getUsers().subscribe({
    error: err => {
      expect(err.message).toContain('Server error');
    },
  });

  const req = httpMock.expectOne('/api/users');
  req.error(new ErrorEvent('Network error'), { status: 500, statusText: 'Server Error' });
});

Quiz

1. How do you override a provider in TestBed?

Question 1 options

2. What does fakeAsync do?

Question 2 options

3. How do you verify a service method was called?

Question 3 options

Flashcards

Question

How to override providers?

Answer

{ provide: Service, useValue: mock } in TestBed providers.

Question

What does fakeAsync do?

Answer

Creates controlled async zone. tick() advances virtual time.

Question

How to verify method calls?

Answer

spyOn(service, 'method') + expect(spy).toHaveBeenCalled()

Revision Notes

Key Takeaways

  • 1. Override providers with useValue
  • 2. fakeAsync + tick for deterministic async testing
  • 3. spyOn for verifying method calls
  • 4. HttpTestingController for HTTP testing

Interview Tips

  • • Explain TestBed provider overriding
  • • Be ready to describe fakeAsync patterns
  • • Mention Jasmine spies for verification

Cheat Sheet

Service Testing Cheat Sheet

Setup:

TestBed.configureTestingModule({
  providers: [
    UserService,
    { provide: HttpClient, useValue: mockHttp },
  ],
});

fakeAsync:

it('...', fakeAsync(() => {
  service.method().subscribe();
  tick(1000);
  expect(result).toBe(expected);
}));

Spies:

spyOn(service, 'method').and.returnValue(of(mockData));
expect(spy).toHaveBeenCalledWith(args);

Continue Learning

Next Topics

Related Reading