Таким образом, вы можете избавиться от ошибок в своем коде Angular.

Хотите пропустить чтение и сразу перейти к коду? Затем нажмите здесь.

Вы когда-нибудь замечали…

Тот…

Общей зависимостью внутри файла службы Angular является HttpClient? 😸

Возможно, ваш опыт работы с Angular отличается от моего, но я считаю, что HttpClient — это распространенная зависимость в службе Angular.

И это правильно.

Лучшей практикой считается размещение вашей логики данных внутри службы, которая может использоваться в приложении. Это оставляет работу по отображению или рендерингу данных вашим внешним компонентам. Принимая во внимание, что ваша служба отвечает за получение этих данных из любого источника.

Но как протестировать службу, которая зависит от HttpClient?

Жил-был молодой разработчик Angular по имени Гарри.

Этот разработчик узнал некоторые базовые вещи о фреймворке Angular и получил задание создать новое блестящее приложение Angular для своей компании.

Гарри начал программировать и делать все, что умел. Он следовал всем лучшим практикам, о которых читал на веб-сайте Angular. Он всегда дважды проверял, чтобы убедиться, что HttpClient никогда не внедрялся ни в один из его компонентов, оставляя вызовы API для службы, которую он затем внедрял в свои компоненты Angular.

Дела шли хорошо…

До того как…

Однажды появился его босс и сказал ему, что прежде чем новое приложение будет запущено в производство, ему нужно написать тесты для всех публичных методов в его сервисах. 🙈 🙈 🙈

«Нет проблем», — сказал Гарри, вернулся к своему компьютеру и начал писать тесты.

Он написал дюжину тестов, когда пришел к сервису, который зависел от модуля HttpClient. Сбитый с толку, он сел, чтобы разобраться во всем этом и попытаться решить, как лучше протестировать этот сервис Angular, зависящий от модуля HttpClient.

Сначала он попытался внедрить модуль HttpClient, но это выдало странную ошибку внедрения.

Поэтому он отправился в Google, чтобы попытаться разобраться в этом, и вместо того, чтобы найти нужные ему разъяснения, его голова вскоре была забита вопросами.

Должен ли я имитировать HTTP-клиент или вызывать реальный сервер API?

Если я заставлю его вызывать настоящий сервер API, то мне сначала нужно пройти аутентификацию на нем, и как мне это сделать?

Как смоделировать HTTP-клиент Angular, не получая ошибки внедрения?

Может, мне просто пропустить тесты для этого сервиса Angular? Думаю, мой босс не узнает.

Итак, как Гарри должен протестировать свой сервис Angular?

Должен ли он использовать модульный тест? Или интеграционный тест?

И именно для этого, мой друг, предназначено это полное руководство по тестированию Angular HTTP-сервисов.

Я собираюсь показать вам и Гарри, как правильно тестировать ваши HTTP-сервисы Angular и помочь избавиться от этих неприятных ошибок в вашем коде.

Стоит ли издеваться над Angular HttpClient? Или вызвать реальный сервис API?

Когда вы пишете тесты для своего сервиса Angular, должны ли вы имитировать HTTP-запросы к вашему сервису данных (API-серверу)?

Или вы действительно должны вызвать реальный сервер?

Что ж… все зависит от того, чего вы хотите добиться, написав тесты для своего HTTP-сервиса.

Иногда при написании тестов для ваших HTTP-сервисов рекомендуется вызывать настоящий сервер API. Но в большинстве случаев эти тесты представляют собой хрупкую интеграцию, которая щелкает, трещит и взрывается прямо у вас перед носом. Они ломаются быстрее и раньше, чем модульные тесты.

Поэтому вместо того, чтобы делать настоящие HTTP-вызовы в тестах Angular, большую часть времени вы должны имитировать HTTP-вызовы. Или, другими словами, пишите модульные тесты вместо интеграционных тестов при тестировании сервисов данных (API).

Кроме того, когда все сделано правильно, модульные тесты пишутся быстрее и дают более высокую рентабельность инвестиций, чем интеграционные тесты, поэтому я рекомендую имитировать HTTP-клиент Angular при тестировании ваших служб данных.

Итак, теперь, когда мы решили смоделировать HTTP-клиент, как нам протестировать службу Angular, которая зависит от модуля HttpClient?

Напишите свой первый модульный тест для службы Angular

Вот самый быстрый способ написать модульный тест для службы Angular, которая зависит от HttpClient.

Сначала установите jasmine-auto-spy.

npm i --include=dev jasmine-auto-spies

А потом написать тест!

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
  let service: CustomersService;
  let httpSpy: Spy<HttpClient>;
  let fakeCustomers: Customer[] = [
    {
      id: "1",
      name: "Fake Customer",
      email: "[email protected]"
    },
    {
      id: "2",
      name: "Fake Customer Two",
      email: "[email protected]"
    }
  ];

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        CustomersService,
        { provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    service = TestBed.inject(CustomersService);
    httpSpy = TestBed.inject<any>(HttpClient);
  });

  it('should return an expected list of customers', (done: DoneFn) => {
    httpSpy.get.and.nextWith(fakeCustomers);

    service.getAllCustomers().subscribe(
      customers => {
        expect(customers).toHaveSize(fakeCustomers.length);
        done();
      },
      done.fail
    );
    expect(httpSpy.get.calls.count()).toBe(1);
  });
});

🎉 🎉 🎉 🎉 🎉

Итак, что мы только что сделали?

Приведенный выше тест взят из простого демо-проекта, который пытается имитировать реальное приложение.

В нашей функции beforeEach мы внедрили HttpClient в качестве шпиона с помощью удобной библиотеки jasmine-auto-spies.

Затем внутри функции it мы имитировали вызов HTTP вместо вызова реального сервера. Затем мы запустили наши тесты, чтобы убедиться, что функция getAllCustomers возвращает желаемый результат.

Написание тестов для POST-запроса

Итак, что если у вас есть POST-запрос, для которого нужно написать тесты?

Опять же, используйте jasmine-auto-spies для создания имитации HTTP-запроса POST.

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
  let service: CustomersService;
  let httpSpy: Spy<HttpClient>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        CustomersService,
        { provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    service = TestBed.inject(CustomersService);
    httpSpy = TestBed.inject<any>(HttpClient);
  });

  it('should create a new customer', (done: DoneFn) => {

    var newCustomer = {
      name: "New Customer",
      email: "[email protected]"
    } as Customer;

    httpSpy.post.and.nextWith(newCustomer);

    service.createCustomer(newCustomer).subscribe(
      customer => {
        expect(customer).toEqual(newCustomer);
        done();
      },
      done.fail
    );
    expect(httpSpy.post.calls.count()).toBe(1);
  });
});

Написание тестов для запроса PUT

Как насчет написания теста для запроса PUT?

Это то же самое, что и в приведенных выше примерах, за исключением того, что мы меняем POST на PUT.

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
  let service: CustomersService;
  let httpSpy: Spy<HttpClient>;
  let fakeCustomers: Customer[] = [
    {
      id: "1",
      name: "Fake Customer",
      email: "[email protected]"
    },
    {
      id: "2",
      name: "Fake Customer Two",
      email: "[email protected]"
    }
  ];

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        CustomersService,
        { provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    service = TestBed.inject(CustomersService);
    httpSpy = TestBed.inject<any>(HttpClient);
  });

  it('should update a customer with given customer id', (done: DoneFn) => {

    var customer = fakeCustomers[0];
    customer.name = "Updated Customer";

    httpSpy.put.and.nextWith(customer);

    service.updateCustomer(customer).subscribe(
      customer => {
        expect(customer.name).toEqual("Updated Customer");
        done();
      },
      done.fail
    );
    expect(httpSpy.put.calls.count()).toBe(1);
  });
});

Написание тестов для запроса DELETE

import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
  let service: CustomersService;
  let httpSpy: Spy<HttpClient>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        CustomersService,
        { provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    service = TestBed.inject(CustomersService);
    httpSpy = TestBed.inject<any>(HttpClient);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should delete an existing customer', (done: DoneFn) => {

    httpSpy.delete.and.nextWith(new HttpResponse ({
      status: 200
    }));

    service.deleteCustomer("1").subscribe(
      response => {
        expect(response.status).toEqual(200);
        done();
      },
      done.fail
    );
    expect(httpSpy.delete.calls.count()).toBe(1);
  });
});

Написание тестов на ошибку 404

И затем, конечно, если вы умный разработчик, вы захотите проверить наличие непредвиденных ошибок.

Итак, как нам проверить ошибку HTTP? Типа 404?

Этот пример был создан для имитации ответа 404, но вы можете изменить его в соответствии со своими желаниями и потребностями.

import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { createSpyFromClass, Spy } from 'jasmine-auto-spies';
import { Customer } from '../models/customer';

import { CustomersService } from './customers.service';

describe('CustomersService', () => {
  let service: CustomersService;
  let httpSpy: Spy<HttpClient>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        CustomersService,
        { provide: HttpClient, useValue: createSpyFromClass(HttpClient) }
      ]
    });

    service = TestBed.inject(CustomersService);
    httpSpy = TestBed.inject<any>(HttpClient);
  });

  it('should return a 404', (done: DoneFn) => {

    var customerId = "89776683";

    httpSpy.get.and.throwWith(new HttpErrorResponse({
          error: "404 - Not Found",
          status: 404
    }));

    service.getCustomer(customerId).subscribe(
      customer => {
        done.fail("Expected a 404");
      },
      error => {
        expect(error.status).toEqual(404);
        done();
      }
    );
    expect(httpSpy.get.calls.count()).toBe(1);
  });
});

Вывод

И это, мой друг, полное руководство по тестированию Angular HTTP-сервисов.

Теперь вы и ваш друг Гарри знаете, как писать простые и быстрые тесты для его сервисов. И Гарри уже вернулся к этому снова. Его проект имеет отличный показатель покрытия кода и почти готов к развертыванию своего нового приложения Angular.

Если вы хотите увидеть весь код этого примера проекта, вы можете найти его здесь.

Если вам понравилась эта статья и вы сочли ее полезной, нажмите кнопку 👏 и подпишитесь на меня, чтобы не пропустить другие интересные статьи, подобные этой.

Подпишитесь на меня: GitHub, Medium, Личный блог

P.S. Если вы один из тех людей (как и я), которые пролистывают страницу до конца, прежде чем прочитать ее, вот о чем речь:

  • Должны ли вы издеваться над HttpClient или вызывать настоящий сервер API?
  • Быстрый способ протестировать HTTP-сервис Angular
  • Выполните примеры тестирования Angular для запросов HttpClient GET, POST, PUT или DELETE.
  • Как смоделировать и протестировать код ответа HTTP

Первоначально опубликовано на https://danielk.tech.