How to Mock Standalone Angular Services and RxJS Timers in Jest (Without Memory Leaks) With Best Practises Of Jest, Angular
A hands-on, memory-efficient, and future-proof approach to mocking HTTP calls, services, and dependencies in Angular standalone applications using Jes

🧠 Introduction
Have you ever struggled to mock services, constants, timers, or injected dependencies in Angular—especially after switching to standalone components?
You're not alone.
With Angular moving towards standalone APIs and modular design, our testing practices must evolve too. If you're still using TestBed.configureTestingModule() for everything—you might be doing more than you need.
In this guide, you'll learn exactly how to mock Angular services, RxJS-based timers, external libraries, constants, and directives in Jest, using modern Angular testing practices that are clean, scalable, and memory-safe.
🎯 What You’ll Learn
How to test standalone Angular services with Jest
Mocking HTTP requests using
HttpClientTestingModulewith standalone setupHandling dependency injection with external libraries, constants, pipes, or directives
Simulating timers and RxJS
interval,timer, orsetTimeoutwithjest.useFakeTimers()Making Jest tests memory-efficient (why and how)
Practical code demos for each use case
Jest best practice
Angular best practice
🔧 Example: Standalone Angular Service
📦 user.service.ts
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { API_URL } from './tokens'; // a custom InjectionToken
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private baseUrl = inject(API_URL); // Using custom token
getUser(id: number): Observable<any> {
return this.http.get(`${this.baseUrl}/users/${id}`);
}
getUserWithDelay(id: number): Observable<any> {
return timer(1000).pipe(
map(() => ({ id, name: 'Delayed User' }))
);
}
}
✅ Unit Test Using Standalone Jest Setup
🔬 user.service.spec.ts
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { API_URL } from './tokens';
import { jest } from '@jest/globals';
describe('UserService (Standalone + Jest)', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
UserService,
{ provide: API_URL, useValue: '<https://mock.api>' }
]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify(); // ✅ Ensures no memory leaks from open HTTP calls
jest.clearAllTimers(); // ✅ Reset timers between tests
});
it('should fetch user by ID', () => {
const mockUser = { id: 1, name: 'Jane' };
service.getUser(1).subscribe(user => {
expect(user).toEqual(mockUser);
});
const req = httpMock.expectOne('<https://mock.api/users/1>');
expect(req.request.method).toBe('GET');
req.flush(mockUser);
});
it('should return delayed user with fake timer', () => {
jest.useFakeTimers(); // ✅ Prevent real timer execution
let result: any;
service.getUserWithDelay(2).subscribe(user => (result = user));
jest.advanceTimersByTime(1000); // Simulate delay
expect(result).toEqual({ id: 2, name: 'Delayed User' });
});
});
🧠 Section: Spying and Mocking With jest.fn()
How to:
Replace service methods
Simulate return values or throw errors
Combine
jest.spyOn()with dependency injection
const mockUserService = {
getUser: jest.fn().mockReturnValue(of({ id: 1, name: 'Mock User' })),
};
🛠️ Section: Error Handling & Retry Tests
Demo:
Simulating 500 errors
Handling retries with
retry()operatorVerifying failure UI states
📦 Bonus: Shared Mocks & DRY Test Setup
How to:
Create reusable
mock-user.service.tsAbstract mocks in
__mocks__folder for auto-mockingMock with dynamic data using factory functions
🚀 Advanced Use Case: Injecting Pipes and Directives in Angular Services
❓ Why would a service inject a pipe or directive?
You may be using custom pipes for transformation logic you want to reuse (e.g., formatting currency, date, slugs).
You might inject a directive that provides some shared behavior or config via
@Directive({ providers: [...] }).
👉 These scenarios are uncommon but powerful—especially in design systems, custom form libraries, or rendering engines.
🎯 Demo Use Case: Injecting a Pipe and a Directive in a Service
🧩 slugify.pipe.ts
import { Pipe, PipeTransform, inject } from '@angular/core';
@Pipe({ name: 'slugify', standalone: true })
export class SlugifyPipe implements PipeTransform {
transform(value: string): string {
return value.toLowerCase().replace(/\\s+/g, '-');
}
}
📍 feature-toggle.directive.ts
import { Directive, inject } from '@angular/core';
@Directive({
selector: '[appFeatureToggle]',
standalone: true,
providers: [{ provide: FeatureToggleDirective, useExisting: FeatureToggleDirective }]
})
export class FeatureToggleDirective {
isFeatureEnabled = true; // Could be dynamically set
}
💡 seo.service.ts — Using Both
import { Injectable, inject } from '@angular/core';
import { SlugifyPipe } from './slugify.pipe';
import { FeatureToggleDirective } from './feature-toggle.directive';
@Injectable({ providedIn: 'root' })
export class SeoService {
private slugify = inject(SlugifyPipe);
private featureToggle = inject(FeatureToggleDirective);
getSeoSlug(title: string): string {
if (this.featureToggle.isFeatureEnabled) {
return this.slugify.transform(title);
}
return 'seo-disabled';
}
}
✅ Jest Unit Test: Mocking Pipes and Directives
🧪 seo.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { SeoService } from './seo.service';
import { SlugifyPipe } from './slugify.pipe';
import { FeatureToggleDirective } from './feature-toggle.directive';
describe('SeoService with Pipe & Directive Injection', () => {
let service: SeoService;
const mockSlugifyPipe: Partial<SlugifyPipe> = {
transform: jest.fn().mockImplementation((s: string) => `slug--${s}`)
};
const mockFeatureToggleDirective: Partial<FeatureToggleDirective> = {
isFeatureEnabled: true
};
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
SeoService,
{ provide: SlugifyPipe, useValue: mockSlugifyPipe },
{ provide: FeatureToggleDirective, useValue: mockFeatureToggleDirective }
]
});
service = TestBed.inject(SeoService);
});
it('should return slugified title when feature is enabled', () => {
const result = service.getSeoSlug('Hello World');
expect(result).toBe('slug--Hello World');
});
it('should return "seo-disabled" if feature is off', () => {
(mockFeatureToggleDirective.isFeatureEnabled as boolean) = false;
const result = service.getSeoSlug('Hello World');
expect(result).toBe('seo-disabled');
});
});
🧵 Real Developer Talk: Why You Should Care
💡 The standalone approach is Angular’s future—and Jest is your ticket to faster, smarter, and more scalable tests.
Say goodbye to heavy test modules and long setup code.
Say hello to on-demand injection, fast feedback loops, and confidence in every refactor.
📌 Best Practices Summary
✅ Angular Testing Best Practices
Use
inject()instead of constructor injection where practicalAvoid global providers when not needed
Always call
httpMock.verify()inafterEachUse
jest.useFakeTimers()+jest.clearAllTimers()for memory-safe timer mocks
✅ Jest Best Practices
Prefer
jest.fn()for mocks, not manual stubsUse
jest.mock()orjest.spyOn()for external dependenciesClean up side effects (
timers,mocks) inafterEachKeep test cases small, focused, and stateless
🧠 Takeaway Best Practices
✅ Never directly depend on DOM-based directive logic in services, unless:
The directive is logic-based (feature flags, configuration, etc.)
It’s provided via DI using
useExisting
✅ For pipes, prefer:
@Pipe({ standalone: true })with properprovidedIninjectionCreating mock pipes using
jest.fn()in unit tests
✅ Avoid leaking real logic in tests. Always:
Use fake timers
Use jest mocks for all dependencies
Avoid DOM usage in unit tests
🎯 Your Turn, Devs!
👀 Did this article spark new ideas or help solve a real problem?
💬 I'd love to hear about it!
✅ Are you already using this technique in your Angular or frontend project?
🧠 Got questions, doubts, or your own twist on the approach?
Drop them in the comments below — let’s learn together!
🙌 Let’s Grow Together!
If this article added value to your dev journey:
🔁 Share it with your team, tech friends, or community — you never know who might need it right now.
📌 Save it for later and revisit as a quick reference.
🚀 Follow Me for More Angular & Frontend Goodness:
I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.
💼 LinkedIn — Let’s connect professionally
🎥 Threads — Short-form frontend insights
🐦 X (Twitter) — Developer banter + code snippets
👥 BlueSky — Stay up to date on frontend trends
🌟 GitHub Projects — Explore code in action
🌐 Website — Everything in one place
📚 Medium Blog — Long-form content and deep-dives
💬 Dev Blog — Free Long-form content and deep-dives
✉️ Substack — Weekly frontend stories & curated resources
🧩 Portfolio — Projects, talks, and recognitions
✍️ Hashnode — Developer blog posts & tech discussions
🎉 If you found this article valuable:
Leave a 👏 Clap
Drop a 💬 Comment
Hit 🔔 Follow for more weekly frontend insights
Let’s build cleaner, faster, and smarter web apps — together.
Stay tuned for more Angular tips, patterns, and performance tricks! 🧪🧠






