Как мокировать сервисы в тестах?angular-96

Основные подходы к мокированию сервисов

1. Использование Jasmine Spy

describe('MyComponent', () => {
  let mockService = jasmine.createSpyObj('DataService', ['getData', 'saveData']);

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: DataService, useValue: mockService }
      ]
    });
  });

  it('should call getData', () => {
    mockService.getData.and.returnValue(of([1, 2, 3]));
    fixture.detectChanges();
    expect(mockService.getData).toHaveBeenCalled();
  });
});

2. Создание полного mock-класса

class MockDataService {
  getData = jasmine.createSpy('getData').and.returnValue(of([]));
  saveData = jasmine.createSpy('saveData').and.returnValue(of({ success: true }));
}

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      { provide: DataService, useClass: MockDataService }
    ]
  });
});

3. Мокирование с частичной реализацией

const mockService = {
  getData: () => of([{ id: 1, name: 'Test' }]),
  saveData: (data: any) => of({ id: 123, ...data })
};

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      { provide: DataService, useValue: mockService }
    ]
  });
});

Мокирование HTTP-запросов

Использование HttpClientTestingModule

import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('DataService', () => {
  let httpMock: HttpTestingController;
  let service: DataService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [DataService]
    });

    service = TestBed.inject(DataService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('should return expected data', () => {
    const testData = { id: 1, name: 'Test' };

    service.getData(1).subscribe(data => {
      expect(data).toEqual(testData);
    });

    const req = httpMock.expectOne('api/data/1');
    expect(req.request.method).toBe('GET');
    req.flush(testData);
  });

  afterEach(() => {
    httpMock.verify(); // Проверяет, что нет незавершенных запросов
  });
});

Мокирование сложных сервисов

1. Мокирование Router

const routerMock = {
  navigate: jasmine.createSpy('navigate'),
  events: of(new NavigationEnd(0, '/test', '/test'))
};

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      { provide: Router, useValue: routerMock }
    ]
  });
});

2. Мокирование ActivatedRoute

const activatedRouteMock = {
  snapshot: {
    paramMap: convertToParamMap({ id: '123' }),
    queryParamMap: convertToParamMap({ filter: 'active' })
  },
  params: of({ id: '123' })
};

Продвинутые техники мокирования

1. Мокирование с зависимостями

class MockLogger {
  log = jasmine.createSpy('log');
}

class MockDataService {
  constructor(private logger: MockLogger) {}

  getData() {
    this.logger.log('Getting data');
    return of([]);
  }
}

beforeEach(() => {
  const logger = new MockLogger();

  TestBed.configureTestingModule({
    providers: [
      { provide: Logger, useValue: logger },
      { provide: DataService, useFactory: () => new MockDataService(logger) }
    ]
  });
});

2. Автоматическое мокирование с ng-mocks

import { MockService } from 'ng-mocks';

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [
      { provide: DataService, useValue: MockService(DataService) }
    ]
  });
});

3. Мокирование методов с разными сценариями

it('should handle error', () => {
  mockService.getData.and.returnValue(throwError('Error'));
  component.loadData();
  fixture.detectChanges();
  expect(component.error).toBeTruthy();
});

it('should handle empty response', () => {
  mockService.getData.and.returnValue(of([]));
  component.loadData();
  fixture.detectChanges();
  expect(component.isEmpty).toBeTruthy();
});

Лучшие практики мокирования

  1. Не мокируйте без необходимости - тестируйте реальные сервисы, когда это возможно
  2. Используйте TypeScript для сохранения типизации в моках
  3. Избегайте излишней детализации - тестируйте поведение, а не реализацию
  4. Поддерживайте моки в актуальном состоянии при изменении сервисов
  5. Выносите общие моки в отдельные файлы (testing/mocks)

Пример структуры моков в проекте

src/
  app/
    services/
      data.service.ts
    testing/
      mocks/
        data.service.mock.ts
        http-client.mock.ts
        router.mock.ts

Резюмируем

Мокирование сервисов в Angular тестах можно выполнять различными способами - от простых spy-объектов до полноценных mock-классов. Выбор подхода зависит от сложности тестируемого компонента и необходимой гибкости. Для HTTP-запросов используйте HttpClientTestingModule, для роутера - специальные mock-объекты. Помните, что цель мокирования - изолировать тестируемый код от внешних зависимостей, а не создать сложную систему моков.