Claude
Skills
Sign in
Back

mocking-patterns

Included with Lifetime
$97 forever

Comprehensive guide to mocking patterns, spies, stubs, and test doubles for Angular testing

Code Review

What this skill does


# 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<User> {
    return of({ id, name: 'Test User', email: '[email protected]' });
  }
}

// 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<boolean> {
    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<UserService>;

  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<UserService>;
    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<AuthService>;

  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<HttpClient>;
  let cache: jasmine.SpyObj<CacheService>;
  let logger: jasmine.SpyObj<LoggerService>;

  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<HttpClient>;
    cache = TestBed.inject(CacheService) as jasmine.SpyObj<CacheService>;
    logger = TestBed.inject(LoggerService) as jasmine.SpyObj<LoggerService>;
  });

  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

Related in Code Review