# Angular Mocking Patterns Comprehensive guide to mocking patterns, test doubles, spies, and HTTP testing in Angular applications. ## Table of Contents 1. [Test Doubles Overview](#test-doubles-overview) 2. [Jasmine Spies](#jasmine-spies) 3. [Service Mocking](#service-mocking) 4. [HTTP Mocking](#http-mocking) 5. [Observable Mocking](#observable-mocking) 6. [Component Mocking](#component-mocking) 7. [Router & Location Mocking](#router--location-mocking) 8. [Form Mocking](#form-mocking) 9. [LocalStorage & SessionStorage](#localstorage--sessionstorage) 10. [Advanced Patterns](#advanced-patterns) --- ## Test Doubles Overview ### Types of Test Doubles ```typescript // 1. DUMMY - Passed but never used class DummyLogger implements Logger { log(message: string): void { // Does nothing } } // 2. STUB - Returns predefined responses class StubUserService { getUser(id: number): Observable { return of({ id, name: 'Test User', email: 'test@example.com' }); } } // 3. SPY - Records interactions const spy = jasmine.createSpy('getData'); spy.and.returnValue('mock data'); // 4. MOCK - Pre-programmed with expectations const mock = jasmine.createSpyObj('UserService', ['getUser', 'updateUser']); mock.getUser.and.returnValue(of({ id: 1, name: 'John' })); // 5. FAKE - Working simplified implementation class FakeAuthService { private authenticated = false; login(): Observable { this.authenticated = true; return of(true); } isAuthenticated(): boolean { return this.authenticated; } } ``` ### When to Use Each | Type | Use When | Example | |------|----------|---------| | Dummy | Need to pass parameters | Logger that's never called | | Stub | Need consistent responses | API returning test data | | Spy | Need to verify calls | Track method invocations | | Mock | Need behavior verification | Service with expectations | | Fake | Need working alternative | In-memory database | --- ## Jasmine Spies ### Creating Spies ```typescript // Method 1: Standalone spy const spy = jasmine.createSpy('myFunction'); // Method 2: Spy on existing object const service = new UserService(); spyOn(service, 'getData'); // Method 3: Spy object (multiple methods) const spyObj = jasmine.createSpyObj('UserService', [ 'getUser', 'updateUser', 'deleteUser' ]); // Method 4: Spy object with properties const spyObjWithProps = jasmine.createSpyObj('UserService', ['getUser'], // methods { currentUser: { id: 1, name: 'John' } } // properties ); ``` ### Spy Return Values ```typescript // Return single value spy.and.returnValue('mock value'); // Return multiple values in sequence spy.and.returnValues('first', 'second', 'third'); // Return Observable spy.and.returnValue(of({ id: 1, name: 'John' })); // Custom implementation spy.and.callFake((arg) => { if (arg === 'admin') { return of({ role: 'admin' }); } return of({ role: 'user' }); }); // Call original method spy.and.callThrough(); // Throw error spy.and.throwError('Something went wrong'); // Return promise spy.and.resolveTo({ id: 1 }); // Promise.resolve spy.and.rejectWith(new Error('Failed')); // Promise.reject ``` ### Spy Assertions ```typescript // Basic assertions expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledTimes(2); expect(spy).not.toHaveBeenCalled(); // Argument assertions expect(spy).toHaveBeenCalledWith('arg1', 'arg2'); expect(spy).toHaveBeenCalledWith(jasmine.any(String)); expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({ id: 1 })); // Order assertions expect(spy1).toHaveBeenCalledBefore(spy2); // Most recent call expect(spy).toHaveBeenCalledWith('last call args'); // Reset spy spy.calls.reset(); ``` ### Spy Properties ```typescript const spy = jasmine.createSpy('myFunction'); spy('arg1', 'arg2'); // Access call information spy.calls.count(); // 1 spy.calls.argsFor(0); // ['arg1', 'arg2'] spy.calls.allArgs(); // [['arg1', 'arg2']] spy.calls.all(); // Full call details spy.calls.mostRecent(); // Last call details spy.calls.first(); // First call details ``` --- ## Service Mocking ### Simple Service Mock ```typescript describe('UserComponent', () => { let component: UserComponent; let userService: jasmine.SpyObj; beforeEach(() => { // Create spy object with all methods const spy = jasmine.createSpyObj('UserService', [ 'getUser', 'getUsers', 'updateUser', 'deleteUser' ]); TestBed.configureTestingModule({ declarations: [UserComponent], providers: [ { provide: UserService, useValue: spy } ] }); userService = TestBed.inject(UserService) as jasmine.SpyObj; component = new UserComponent(userService); }); it('should load user on init', () => { const mockUser = { id: 1, name: 'John Doe' }; userService.getUser.and.returnValue(of(mockUser)); component.ngOnInit(); expect(userService.getUser).toHaveBeenCalledWith(1); expect(component.user).toEqual(mockUser); }); }); ``` ### Service with Properties ```typescript describe('AuthComponent', () => { let authService: jasmine.SpyObj; beforeEach(() => { authService = jasmine.createSpyObj( 'AuthService', ['login', 'logout'], { currentUser: of({ id: 1, name: 'John' }), isAuthenticated: true } ); }); it('should display current user', () => { expect(authService.isAuthenticated).toBe(true); authService.currentUser.subscribe(user => { expect(user.name).toBe('John'); }); }); }); ``` ### Mocking Service Dependencies ```typescript describe('ProductService', () => { let service: ProductService; let http: jasmine.SpyObj; let cache: jasmine.SpyObj; let logger: jasmine.SpyObj; beforeEach(() => { const httpSpy = jasmine.createSpyObj('HttpClient', ['get', 'post', 'put', 'delete']); const cacheSpy = jasmine.createSpyObj('CacheService', ['get', 'set', 'clear']); const loggerSpy = jasmine.createSpyObj('LoggerService', ['log', 'error']); TestBed.configureTestingModule({ providers: [ ProductService, { provide: HttpClient, useValue: httpSpy }, { provide: CacheService, useValue: cacheSpy }, { provide: LoggerService, useValue: loggerSpy } ] }); service = TestBed.inject(ProductService); http = TestBed.inject(HttpClient) as jasmine.SpyObj; cache = TestBed.inject(CacheService) as jasmine.SpyObj; logger = TestBed.inject(LoggerService) as jasmine.SpyObj; }); it('should use cached data when available', () => { const cachedProduct = { id: 1, name: 'Cached Product' }; cache.get.and.returnValue(cachedProduct); service.getProduct(1).subscribe(product => { expect(product).toEqual(cachedProduct); expect(http.get).not.toHaveBeenCalled(); expect(logger.log).toHaveBeenCalledWith('Using cached product'); }); }); }); ``` --- ## HTTP Mocking ### HttpClientTestingModule Setup ```typescript import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('ApiService', () => { let service: ApiService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ApiService] }); service = TestBed.inject(ApiService); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { httpMock.verify(); // Verify no outstanding requests }); }); ``` ### GET Request Mocking ```typescript it('should fetch users', () => { const mockUsers = [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' } ]; service.getUsers().subscribe(users => { expect(users).toEqual(mockUsers); expect(users.length).toBe(2); }); const req = httpMock.expectOne('/api/users'); expect(req.request.method).toBe('GET'); req.flush(mockUsers); }); it('should handle query parameters', () => { service.getUsers({ role: 'admin', active: true }).subscribe(); const req = httpMock.expectOne(req => req.url === '/api/users' && req.params.get('role') === 'admin' && req.params.get('active') === 'true' ); req.flush([]); }); ``` ### POST/PUT/DELETE Mocking ```typescript it('should create user', () => { const newUser = { name: 'John', email: 'john@example.com' }; const createdUser = { id: 1, ...newUser }; service.createUser(newUser).subscribe(user => { expect(user).toEqual(createdUser); }); const req = httpMock.expectOne('/api/users'); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(newUser); req.flush(createdUser); }); it('should update user', () => { const updatedUser = { id: 1, name: 'John Updated' }; service.updateUser(1, updatedUser).subscribe(); const req = httpMock.expectOne('/api/users/1'); expect(req.request.method).toBe('PUT'); req.flush(updatedUser); }); it('should delete user', () => { service.deleteUser(1).subscribe(); const req = httpMock.expectOne('/api/users/1'); expect(req.request.method).toBe('DELETE'); req.flush(null); }); ``` ### HTTP Headers Mocking ```typescript it('should send authorization header', () => { service.getProtectedData().subscribe(); const req = httpMock.expectOne('/api/protected'); expect(req.request.headers.get('Authorization')).toBe('Bearer fake-token'); req.flush({ data: 'secret' }); }); it('should set custom headers', () => { service.uploadFile(new FormData()).subscribe(); const req = httpMock.expectOne('/api/upload'); expect(req.request.headers.get('Content-Type')).toContain('multipart/form-data'); req.flush({ success: true }); }); ``` ### HTTP Error Handling ```typescript it('should handle 404 error', () => { service.getUser(999).subscribe({ next: () => fail('should have failed'), error: (error) => { expect(error.status).toBe(404); expect(error.statusText).toBe('Not Found'); } }); const req = httpMock.expectOne('/api/users/999'); req.flush('User not found', { status: 404, statusText: 'Not Found' }); }); it('should handle 500 error with message', () => { service.updateUser(1, {}).subscribe({ error: (error) => { expect(error.status).toBe(500); expect(error.error.message).toBe('Server error'); } }); const req = httpMock.expectOne('/api/users/1'); req.flush( { message: 'Server error' }, { status: 500, statusText: 'Internal Server Error' } ); }); it('should handle network error', () => { service.getUsers().subscribe({ error: (error) => { expect(error.error.type).toBe('error'); } }); const req = httpMock.expectOne('/api/users'); req.error(new ErrorEvent('Network error')); }); ``` ### Multiple HTTP Requests ```typescript it('should handle multiple simultaneous requests', () => { service.getUser(1).subscribe(); service.getUser(2).subscribe(); service.getUser(3).subscribe(); const requests = httpMock.match(req => req.url.startsWith('/api/users/')); expect(requests.length).toBe(3); requests[0].flush({ id: 1, name: 'User 1' }); requests[1].flush({ id: 2, name: 'User 2' }); requests[2].flush({ id: 3, name: 'User 3' }); }); it('should handle sequential dependent requests', fakeAsync(() => { let userData: any; let postsData: any; // First request - get user service.getUser(1).subscribe(user => { userData = user; }); let req = httpMock.expectOne('/api/users/1'); req.flush({ id: 1, name: 'John' }); tick(); // Second request - get user's posts (depends on first) service.getUserPosts(userData.id).subscribe(posts => { postsData = posts; }); req = httpMock.expectOne('/api/users/1/posts'); req.flush([{ id: 1, title: 'Post 1' }]); tick(); expect(userData).toBeDefined(); expect(postsData.length).toBe(1); })); ``` --- ## Observable Mocking ### Basic Observable Mocking ```typescript import { of, throwError, EMPTY, NEVER } from 'rxjs'; import { delay } from 'rxjs/operators'; // Success response service.getData.and.returnValue(of({ data: 'test' })); // Error response service.getData.and.returnValue( throwError(() => new Error('Failed')) ); // Empty observable service.getData.and.returnValue(EMPTY); // Never completing observable service.getData.and.returnValue(NEVER); // Delayed response service.getData.and.returnValue( of({ data: 'test' }).pipe(delay(1000)) ); ``` ### Subject Mocking ```typescript import { Subject, BehaviorSubject, ReplaySubject } from 'rxjs'; describe('NotificationService', () => { let service: NotificationService; let notificationSubject: Subject; beforeEach(() => { notificationSubject = new Subject(); const spy = jasmine.createSpyObj('NotificationService', ['notify'], { notifications$: notificationSubject.asObservable() } ); service = spy; }); it('should receive notifications', (done) => { const messages: string[] = []; service.notifications$.subscribe(msg => { messages.push(msg); if (messages.length === 2) { expect(messages).toEqual(['Hello', 'World']); done(); } }); notificationSubject.next('Hello'); notificationSubject.next('World'); }); }); // BehaviorSubject with initial value it('should have initial value', () => { const subject = new BehaviorSubject(0); const spy = jasmine.createSpyObj('CounterService', ['increment'], { count$: subject.asObservable() } ); spy.count$.subscribe(count => { expect(count).toBe(0); }); subject.next(1); spy.count$.subscribe(count => { expect(count).toBe(1); }); }); ``` ### Async Observable Testing ```typescript it('should handle async data', fakeAsync(() => { let result: any; service.getData().pipe( delay(1000) ).subscribe(data => { result = data; }); expect(result).toBeUndefined(); tick(1000); // Advance time expect(result).toEqual({ data: 'test' }); })); it('should handle debounced input', fakeAsync(() => { const spy = jasmine.createSpy('callback'); component.searchInput.pipe( debounceTime(300) ).subscribe(spy); component.searchInput.next('a'); tick(100); component.searchInput.next('ab'); tick(100); component.searchInput.next('abc'); tick(300); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith('abc'); })); ``` --- ## Component Mocking ### Mock Child Components ```typescript // Create mock component @Component({ selector: 'app-child', template: '
Mock Child
' }) class MockChildComponent { @Input() data: any; @Output() action = new EventEmitter(); } describe('ParentComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ ParentComponent, MockChildComponent // Use mock instead of real ] }).compileComponents(); }); it('should pass data to child', () => { const fixture = TestBed.createComponent(ParentComponent); const component = fixture.componentInstance; const childDebugElement = fixture.debugElement.query( By.directive(MockChildComponent) ); const childComponent = childDebugElement.componentInstance as MockChildComponent; component.parentData = { value: 'test' }; fixture.detectChanges(); expect(childComponent.data).toEqual({ value: 'test' }); }); it('should handle child events', () => { const fixture = TestBed.createComponent(ParentComponent); const component = fixture.componentInstance; const childDebugElement = fixture.debugElement.query( By.directive(MockChildComponent) ); const childComponent = childDebugElement.componentInstance as MockChildComponent; spyOn(component, 'handleAction'); childComponent.action.emit('test-data'); expect(component.handleAction).toHaveBeenCalledWith('test-data'); }); }); ``` ### NO_ERRORS_SCHEMA ```typescript // Alternative: Use NO_ERRORS_SCHEMA to ignore child components import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('ParentComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ParentComponent], schemas: [NO_ERRORS_SCHEMA] // Ignore unknown elements }).compileComponents(); }); it('should work without child component implementation', () => { const fixture = TestBed.createComponent(ParentComponent); expect(fixture.componentInstance).toBeTruthy(); }); }); ``` --- ## Router & Location Mocking ### Router Mocking ```typescript import { Router } from '@angular/router'; describe('NavigationComponent', () => { let component: NavigationComponent; let router: jasmine.SpyObj; beforeEach(() => { const routerSpy = jasmine.createSpyObj('Router', [ 'navigate', 'navigateByUrl' ]); TestBed.configureTestingModule({ declarations: [NavigationComponent], providers: [ { provide: Router, useValue: routerSpy } ] }); router = TestBed.inject(Router) as jasmine.SpyObj; component = new NavigationComponent(router); }); it('should navigate to dashboard', () => { component.goToDashboard(); expect(router.navigate).toHaveBeenCalledWith(['/dashboard']); }); it('should navigate with query params', () => { component.goToProducts({ category: 'electronics' }); expect(router.navigate).toHaveBeenCalledWith( ['/products'], { queryParams: { category: 'electronics' } } ); }); }); ``` ### ActivatedRoute Mocking ```typescript import { ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; describe('ProductDetailComponent', () => { let component: ProductDetailComponent; let route: ActivatedRoute; beforeEach(() => { const routeMock = { snapshot: { paramMap: { get: (key: string) => '123' } }, params: of({ id: '123' }), queryParams: of({ tab: 'details' }), data: of({ product: { id: 1, name: 'Test' } }) }; TestBed.configureTestingModule({ declarations: [ProductDetailComponent], providers: [ { provide: ActivatedRoute, useValue: routeMock } ] }); route = TestBed.inject(ActivatedRoute); component = new ProductDetailComponent(route); }); it('should read route params', () => { expect(component.productId).toBe('123'); }); it('should subscribe to query params', () => { component.ngOnInit(); expect(component.activeTab).toBe('details'); }); }); ``` ### RouterTestingModule ```typescript import { RouterTestingModule } from '@angular/router/testing'; import { Location } from '@angular/common'; import { Router } from '@angular/router'; describe('Navigation Integration', () => { let router: Router; let location: Location; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: 'home', component: HomeComponent }, { path: 'about', component: AboutComponent } ]) ], declarations: [HomeComponent, AboutComponent] }).compileComponents(); router = TestBed.inject(Router); location = TestBed.inject(Location); }); it('should navigate to home', fakeAsync(() => { router.navigate(['/home']); tick(); expect(location.path()).toBe('/home'); })); }); ``` --- ## Form Mocking ### FormBuilder & FormControl Mocking ```typescript import { FormBuilder, FormGroup, Validators } from '@angular/forms'; describe('UserFormComponent', () => { let component: UserFormComponent; let fb: FormBuilder; beforeEach(() => { fb = new FormBuilder(); component = new UserFormComponent(fb); component.ngOnInit(); }); it('should create form with controls', () => { expect(component.userForm.get('name')).toBeDefined(); expect(component.userForm.get('email')).toBeDefined(); }); it('should validate required fields', () => { const name = component.userForm.get('name'); expect(name?.valid).toBeFalsy(); expect(name?.hasError('required')).toBeTruthy(); name?.setValue('John Doe'); expect(name?.valid).toBeTruthy(); }); it('should validate email format', () => { const email = component.userForm.get('email'); email?.setValue('invalid-email'); expect(email?.hasError('email')).toBeTruthy(); email?.setValue('valid@example.com'); expect(email?.valid).toBeTruthy(); }); }); ``` ### Custom Validators Mocking ```typescript // Custom validator export function ageValidator(min: number, max: number): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const age = control.value; if (age < min || age > max) { return { ageRange: { min, max, actual: age } }; } return null; }; } // Testing custom validator describe('Age Validator', () => { it('should validate age range', () => { const control = new FormControl(25); const validator = ageValidator(18, 65); expect(validator(control)).toBeNull(); // Valid control.setValue(10); expect(validator(control)).toEqual({ ageRange: { min: 18, max: 65, actual: 10 } }); control.setValue(70); expect(validator(control)).toEqual({ ageRange: { min: 18, max: 65, actual: 70 } }); }); }); ``` --- ## LocalStorage & SessionStorage ### Storage Mocking ```typescript describe('StorageService', () => { let service: StorageService; let store: { [key: string]: string }; beforeEach(() => { store = {}; spyOn(localStorage, 'getItem').and.callFake((key: string) => { return store[key] || null; }); spyOn(localStorage, 'setItem').and.callFake((key: string, value: string) => { store[key] = value; }); spyOn(localStorage, 'removeItem').and.callFake((key: string) => { delete store[key]; }); spyOn(localStorage, 'clear').and.callFake(() => { store = {}; }); service = new StorageService(); }); it('should save data to localStorage', () => { service.setItem('key', 'value'); expect(localStorage.setItem).toHaveBeenCalledWith('key', 'value'); expect(store['key']).toBe('value'); }); it('should retrieve data from localStorage', () => { store['key'] = 'value'; const result = service.getItem('key'); expect(localStorage.getItem).toHaveBeenCalledWith('key'); expect(result).toBe('value'); }); it('should remove item from localStorage', () => { store['key'] = 'value'; service.removeItem('key'); expect(localStorage.removeItem).toHaveBeenCalledWith('key'); expect(store['key']).toBeUndefined(); }); }); ``` --- ## Advanced Patterns ### Partial Mocking ```typescript // Mock only specific methods, keep others real describe('ComplexService', () => { let service: ComplexService; beforeEach(() => { service = new ComplexService(); spyOn(service, 'expensiveOperation').and.returnValue('mocked'); // Other methods use real implementation }); it('should use mocked method', () => { const result = service.doSomething(); expect(service.expensiveOperation).toHaveBeenCalled(); }); }); ``` ### Spy Chaining ```typescript it('should chain multiple service calls', () => { const userService = jasmine.createSpyObj('UserService', ['getUser']); const orderService = jasmine.createSpyObj('OrderService', ['getOrders']); userService.getUser.and.returnValue(of({ id: 1, name: 'John' })); orderService.getOrders.and.returnValue(of([{ id: 1, total: 100 }])); component.loadUserData(1); expect(userService.getUser).toHaveBeenCalledWith(1); expect(orderService.getOrders).toHaveBeenCalledWith(1); }); ``` ### Conditional Mocking ```typescript it('should mock based on arguments', () => { const service = jasmine.createSpyObj('ApiService', ['getData']); service.getData.and.callFake((id: number) => { if (id === 1) { return of({ data: 'admin' }); } else if (id === 2) { return of({ data: 'user' }); } else { return throwError(() => new Error('Not found')); } }); service.getData(1).subscribe(result => { expect(result.data).toBe('admin'); }); service.getData(2).subscribe(result => { expect(result.data).toBe('user'); }); service.getData(999).subscribe({ error: (error) => { expect(error.message).toBe('Not found'); } }); }); ``` ### Dynamic Mock Configuration ```typescript describe('ConfigurableService', () => { let service: MyService; let apiService: jasmine.SpyObj; function configureTest(config: { shouldSucceed: boolean; delay?: number; returnValue?: any; }) { if (config.shouldSucceed) { let obs = of(config.returnValue || { success: true }); if (config.delay) { obs = obs.pipe(delay(config.delay)); } apiService.getData.and.returnValue(obs); } else { apiService.getData.and.returnValue( throwError(() => new Error('Failed')) ); } } beforeEach(() => { apiService = jasmine.createSpyObj('ApiService', ['getData']); service = new MyService(apiService); }); it('should handle success case', () => { configureTest({ shouldSucceed: true, returnValue: { data: 'test' } }); service.loadData().subscribe(result => { expect(result.data).toBe('test'); }); }); it('should handle error case', () => { configureTest({ shouldSucceed: false }); service.loadData().subscribe({ error: (error) => { expect(error.message).toBe('Failed'); } }); }); }); ``` --- ## Best Practices ### ✅ DO ```typescript // Use spy objects for dependencies const spy = jasmine.createSpyObj('Service', ['method']); // Reset spies between tests afterEach(() => { spy.calls.reset(); }); // Use meaningful test data const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com' }; // Mock at the right level // Component tests: Mock services // Service tests: Mock HTTP // E2E tests: Mock nothing // Verify spy calls explicitly expect(spy.method).toHaveBeenCalledWith('expected', 'arguments'); ``` ### ❌ DON'T ```typescript // Don't use real services in unit tests TestBed.configureTestingModule({ providers: [RealDatabaseService] // ❌ Bad }); // Don't over-mock spyOn(Math, 'random'); // ❌ Don't mock language features spyOn(Array.prototype, 'push'); // ❌ Don't mock prototypes // Don't create brittle mocks spy.and.returnValue({ /* huge object */ }); // ❌ Too specific // Don't forget to verify HTTP calls // Missing httpMock.verify() in afterEach ❌ ``` --- ## Quick Reference ### Common Spy Patterns ```typescript // Return value spy.and.returnValue(value); // Return Observable spy.and.returnValue(of(value)); // Return Promise spy.and.resolveTo(value); // Throw error spy.and.throwError('error'); // Custom logic spy.and.callFake((arg) => { /* logic */ }); // Call real method spy.and.callThrough(); ``` ### Common Assertions ```typescript expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledTimes(n); expect(spy).toHaveBeenCalledWith(arg1, arg2); expect(spy).not.toHaveBeenCalled(); ``` ### HTTP Testing ```typescript const req = httpMock.expectOne(url); expect(req.request.method).toBe('GET'); req.flush(mockData); httpMock.verify(); // In afterEach ``` --- *Master mocking for clean, maintainable tests! 🎭*