Skip to main content

Command Palette

Search for a command to run...

10 Angular Unit Tests to Prevent Memory Leaks You Didn't Know You Needed

Stop Hidden Performance Killers Before They Start: Essential Tests for Robust Apps

Updated
3 min read
10 Angular Unit Tests to Prevent Memory Leaks You Didn't Know You Needed

Master Angular component cleanup with real-world unit tests for setTimeout, Observables, DOM listeners, AsyncPipe, and more. A complete guide to writing leak-free Angular apps.

Perfect — here are 10 comprehensive unit test cases in Angular to ensure memory leaks are avoided. These cover all critical and edge async scenarios, such as:

  • setTimeout

  • setInterval

  • RxJS subscriptions

  • AsyncPipe

  • EventListeners

  • Subjects

  • AnimationFrames

  • WebSocket/Custom cleanup

  • ResizeObserver

  • MutationObserver


✅ 1. Unsubscribe from RxJS with takeUntil

it('should call destroy$.next() and complete() on ngOnDestroy', () => {
  const nextSpy = jest.spyOn((component as any).destroy$, 'next');
  const completeSpy = jest.spyOn((component as any).destroy$, 'complete');

component.ngOnDestroy();
  expect(nextSpy).toHaveBeenCalledTimes(1);
  expect(completeSpy).toHaveBeenCalledTimes(1);
});

✅ 2. Clear setInterval on Destroy

it('should clear interval on destroy', () => {
  const clearSpy = jest.spyOn(window, 'clearInterval');
  component.intervalId = setInterval(() => {}, 1000);

component.ngOnDestroy();
  expect(clearSpy).toHaveBeenCalledWith(component.intervalId);
  clearInterval(component.intervalId); // cleanup
});

✅ 3. Clear setTimeout on Destroy

it('should clear timeout on destroy', () => {
  const clearSpy = jest.spyOn(window, 'clearTimeout');
  component.timeoutId = setTimeout(() => {}, 1000);

component.ngOnDestroy();
  expect(clearSpy).toHaveBeenCalledWith(component.timeoutId);
  clearTimeout(component.timeoutId); // cleanup
});

✅ 4. Remove Event Listener on Destroy

it('should remove event listener on destroy', () => {
  const removeSpy = jest.spyOn(document, 'removeEventListener');
  component.listener = jest.fn();

document.addEventListener('click', component.listener);
  component.ngOnDestroy();
  expect(removeSpy).toHaveBeenCalledWith('click', component.listener);
});

✅ 5. Verify AsyncPipe Usage — No Manual Cleanup Required

it('should have data$ defined for async pipe usage', () => {
  expect(component.data$).toBeDefined();
});

Note: AsyncPipe automatically unsubscribes on destroy.


✅ 6. Unsubscribe from Manual Subject

it('should unsubscribe from subject', () => {
  const sub = new Subject<void>();
  const spy = jest.fn();
  sub.subscribe(spy);

sub.next();
  sub.complete();
  expect(spy).toHaveBeenCalled();
});

✅ 7. Cancel requestAnimationFrame on Destroy

it('should cancel requestAnimationFrame on destroy', () => {
  const cancelSpy = jest.spyOn(window, 'cancelAnimationFrame');
  component.rafId = requestAnimationFrame(() => {});

component.ngOnDestroy();
  expect(cancelSpy).toHaveBeenCalledWith(component.rafId);
});

✅ 8. Close Custom WebSocket or Observer on Destroy

it('should close websocket connection on destroy', () => {
  component.socket = { close: jest.fn() } as any;
  component.ngOnDestroy();

expect(component.socket.close).toHaveBeenCalled();
});

✅ 9. Disconnect ResizeObserver on Destroy

it('should disconnect ResizeObserver on destroy', () => {
  component.resizeObserver = { disconnect: jest.fn() } as any;
  component.ngOnDestroy();

expect(component.resizeObserver.disconnect).toHaveBeenCalled();
});

✅ 10. Disconnect MutationObserver on Destroy

it('should disconnect MutationObserver on destroy', () => {
  component.mutationObserver = { disconnect: jest.fn() } as any;
  component.ngOnDestroy();

expect(component.mutationObserver.disconnect).toHaveBeenCalled();
});

🎯 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! 🧪🧠

✨ Share Your Thoughts To 📣 Set Your Notification Preference