when и expect в сервисе $httpBackend

Сегодня поговорим о unit-тестировании AngularJS-сервисов, которые предоставляют интерфейс для обмена данными с сервером, а если быть точнее, то мы рассмотрим один нюанс — отличие expect’ов от when’ов при работе с $httpBackend‘ом из модуля ngMock. Но давайте будем последовательны и начнём с начала.

Встроенный AngularJS-сервис $http делегирует всю работу по обеспечению кроссбраузерности в работе с XMLHttpRequest низкоуровневому сервису $httpBackend. Напрямую использовать его в коде вашего приложения не рекомендуется. Но в unit-тестировании мы не можем обращаться к реальному серверу, т.к. такой подход нарушает важные критерии unit-тестов: изолированность компонента от всего остального мира, скорость исполнения (при обращении к серверу мы не можем гарантировать, что он быстро ответит). Поэтому при тестировании мы должны при помощи механизма Dependency Injection, предоставляемого фрэймворком, подменить $httpBackend на его «фальшивку», mock-объект из модуля ngMock, который ничего никуда не отправляет, но имеет такой же интерфейс и знает, что и куда хотели через него послать.

При помощи такого mock’а можно (и нужно!) код наших тестов из асинхронного сделать синхронным, т.к. Jasmine не может работать с асинхронностью. Но, как это часто бывает, перед тем, как что-то использовать, необходимо это что-то настроить. Так и тут. Мы должны сообщить $httpBackend’у, каким статусом и телом он должен отвечать на запрос по определённому URL’у. Этот сервис предоставляет разработчикам возможность создавать ожидания запросов («Request Expectations») и определения бэкенда («Backend Definitions»). Методы, создающие ожидания, начинаются со слова «expect» (такие как expectGET, expectPOST и т.д.), а те, что создают определения, начинаются со слова «when» (такие как whenGET, whenPOST и т.д.).

Но всегда при настройке ожиданий запросов и описаний сервера необходимо держать в голове следующие нюансы:

  • Ожидание запроса наделяет тест бОльшей ответственностью, т.к. если создано описание бэкенда, но ни один «HTTP»-запрос во время исполнения теста на него не попал, то на этом тест не завалится. Если же в тесте создано ожидание запроса, и ни один подходящий запрос к нему во время исполнения не поступил, то тест завалится из-за неудовлетворённого ожидания;
  • Создав единственное описание бэкенда, мы можем ответить на множество запросов, приходящих по такому адресу. В ожиданиях запроса всё строже — как только запрос пришёл на ожидание, оно сразу же выходит из «игры» и следующий запрос будет обрабатываться другим ожиданием запроса или описанием бэкенда;
  • Если мы создали несколько описаний бэкенда, то запросы могут приходить в любом порядке, и все они при этом успешно обработаются. Если же мы создадим несколько ожиданий запросов, то запросы должны удовлетворять их в том же порядке, в котором они создавались, иначе мы получим ошибку;
  • В случае с ожиданием запроса указание ответа — необязательно. Если он не задан, то значение ответа будет искаться в подходящем описании бэкенда. Но вот описание бэкенда без указания ответа мы создать не можем! В случае, если у ожидания ответ не указан и подходящего описания бэкенда нет, тест завалится.

И ещё во всех тестах, проверяющих методы с HTTP-взаимодействием, обязательно делайте такие проверки после каждого теста:

afterEach(function() {
    $httpBackend.verifyNoOutstandingRequest();
    $httpBackend.verifyNoOutstandingExpectation();
});

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

По большому счёту, если вы внимательно читали документацию AngularJS, то я вряд ли вам открыл Америку :) Но лично я при знакомстве с сервисом $httpBackend при изучении тестирования почему-то пробежался по разделу с описанием разницы when и expect без должного внимания, поэтому какое-то время писал тесты не совсем так, как сейчас. Если вам интересна тема $httpBackend, то можете ещё почитать мою статью про HTTP-заглушки в AngularJS-приложениях.

На этом на сегодня всё! Надеюсь вам была полезна эта заметка!

До новых статей! :)