# How to Mock Standalone Angular Services and RxJS Timers in Jest (Without Memory Leaks) With Best Practises Of Jest, Angular

## 🧠 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 `HttpClientTestingModule` with standalone setup
    
* Handling **dependency injection** with external libraries, constants, pipes, or directives
    
* Simulating **timers** and RxJS `interval`, `timer`, or `setTimeout` with `jest.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`

```tsx

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`

```tsx

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
    

```tsx

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()` operator
    
* Verifying failure UI states
    

---

## 📦 Bonus: Shared Mocks & DRY Test Setup

How to:

* Create reusable `mock-user.service.ts`
    
* Abstract mocks in `__mocks__` folder for auto-mocking
    
* Mock 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`

```tsx

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`

```tsx

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

```tsx

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`

```tsx

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 practical
    
* Avoid global providers when not needed
    
* Always call `httpMock.verify()` in `afterEach`
    
* Use `jest.useFakeTimers()` + `jest.clearAllTimers()` for memory-safe timer mocks
    

### ✅ Jest Best Practices

* Prefer `jest.fn()` for mocks, not manual stubs
    
* Use `jest.mock()` or `jest.spyOn()` for external dependencies
    
* Clean up side effects (`timers`, `mocks`) in `afterEach`
    
* Keep 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 proper `providedIn` injection
    
* Creating **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**](https://www.linkedin.com/in/errajatmalik/) — Let’s connect professionally
    
* 🎥 [**Threads**](https://www.threads.net/@er.rajatmalik) — Short-form frontend insights
    
* 🐦 [**X (Twitter)**](https://twitter.com/er_rajatmalik) — Developer banter + code snippets
    
* 👥 [**BlueSky**](http://devrajat.bsky.social/) — Stay up to date on frontend trends
    
* 🌟 [**GitHub Projects**](https://github.com/malikrajat) — Explore code in action
    
* 🌐 [**Website**](https://malikrajat.github.io/) — Everything in one place
    
* 📚 [**Medium Blog**](https://medium.com/@codewithrajat/) — Long-form content and deep-dives
    
* 💬 [**Dev Blog**](https://dev.to/codewithrajat) — Free Long-form content and deep-dives
    
* ✉️ [**Substack**](https://codewithrajat.substack.com/) — Weekly frontend stories & curated resources
    
* 🧩 [**Portfolio**](https://rajatmalik.dev/) — Projects, talks, and recognitions
    
* ✍️ [**Hashnode**](https://hashnode.com/@codeswithrajat) — 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!** 🧪🧠

[✨ Share Your Thoughts To 📣 Set Your Notification Preference](https://forms.gle/mdfXEVh3AxYwAsKw5)
