Stop Hidden Performance Killers Before They Start: Essential Tests for Robust Apps
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:
setTimeoutsetIntervalRxJS subscriptionsAsyncPipeEventListenersSubjectsAnimationFramesWebSocket/Custom cleanupResizeObserverMutationObserver
✅ 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! 🧪🧠🚀
Top comments (0)