Статьи

ngImprovedTesting: тестирование для AngularJS стало проще

Возможность легко протестировать ваше приложение — одна из самых мощных функций, предлагаемых AngularJS. Все разработанные вами сервисы, контроллеры, фильтры и даже директивы могут быть полностью протестированы.

Однако кривая обучения написанию (правильных) модульных тестов, как правило, довольно крутая.
Это происходит главным образом потому, что AngularJS на самом деле не предлагает никаких высокоуровневых API для облегчения модульного тестирования. Вместо этого вы вынуждены использовать те же (низкоуровневые) сервисы, которые AngularJS использует для внутреннего использования. Это означает, что вы должны углубленно получить знания о внутренностях $ controller, когда $ digest и как использовать $ обеспечить, чтобы высмеивать эти сервисы. Особенно слишком громоздким является вычерчивание зависимости контроллера, фильтра или другого сервиса.

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

Образец заявки

Рассмотрим следующее приложение, состоящее из «userService» и «missionsService »:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var appModule = angular.module('myApp', []);
  
appModule.factory('userService', function($http) {
    var detailsPerUsername = {};
  
    $http({method: 'GET', url: '/users'})
        .success(function(users) {
            detailsPerUsername = _.indexBy(users, 'username');
        });
  
    return {
        getUserDetails: function(userName) {
            return detailsPerUsername[userName];
        }
    };
});
  
appModule.factory('permissionService', function(users) {
    return {
        hasAdminAccess: function(username) {
            return users.getUserDetails(username).admin === true;
        }
    };
});

Когда дело доходит до модульного тестирования «missionsService », есть две стратегии по умолчанию:

  • использование mock $ httpBackend (из модуля ngMock) для имитации трафика $ http из «userService»
  • использование макета вместо фактической зависимости «userService»

Замена «userService» на макет с использованием ванильного AngularJS

Используя vanilla AngularJS, вы должны выполнять всю тяжелую работу самостоятельно, когда вам нравится создавать макет. Вам придется вручную создать объект с соответствующими полями и методами. Наконец, вам нужно зарегистрировать макет (используя $ provide), чтобы перезаписать существующую реализацию сервиса.

Используя следующий ванильный AngularJS, мы можем заменить «userService» на макет в наших модульных тестах:

01
02
03
04
05
06
07
08
09
10
11
12
13
describe('Vanilla mocked style permissions service specification',
        function() {
    var userServiceMock;
  
    beforeEach(module('myApp', function ($provide) {
        userServiceMock = {
            getUserDetails: jasmine.createSpy()
        };
  
        $provide.value('userService', userServiceMock);
    }));
  
    // ...

Недостатки ванильного стиля издевательства

Возможность имитировать сервисы в модульных тестах — отличная особенность AngularJS, но она далека от совершенства.

Как разработчик, я действительно не хочу беспокоиться о необходимости вручную создавать фиктивный объект. Например, я мог бы просто забыть смоделировать зависимость «userService» при тестировании «missionsService », означая, что я бы случайно протестировал ее с использованием фактического« userService ». А что если вы реорганизуете «userService» и переименуете его метод в «getUserInfo».
Тогда вы бы, кроме модульного теста «allowService», потерпели бы неудачу, верно? Но этого не произойдет, так как у смоделированного «userService» все еще есть старый метод «getUserDetails» (шпион).

Что еще хуже … что если вы переименуете сервис в userInfoService. Это делает зависимость «userService» от «missionsService »более не разрешимой. Благодаря этой модификации приложение больше не будет загружаться при запуске в браузере. Но при выполнении из модульного теста он не потерпит неудачу, поскольку все еще использует свой собственный макет. Однако другие модульные тесты, использующие тот же модуль, но не издевающиеся над сервисом, завершатся неудачно.

Как макетирование может быть улучшено

Исходить из Java-фона, если я нашел ручное создание макетов, мне показалось довольно странным. В статических языках наличие интерфейсов (и классов) упрощает автоматическое создание макетов.

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

Тогда мы могли бы автоматически создавать макеты, которые содержат те же свойства, что и исходный объект. Каждое не относящееся к методу свойство может быть скопировано как есть, и каждый метод вместо этого будет шпионом Жасмина.

Вместо ручной регистрации фиктивного сервиса с использованием $ provide мы могли бы автоматизировать это. Это также позволило бы нам автоматически проверить, существует ли сервис, который вы хотите смоделировать. Кроме того, мы могли бы проверить, действительно ли сервис-макет используется в качестве зависимости компонента.

Представляем библиотеку ngImprovedTesting

Чтобы облегчить (модульное) тестирование, я создал библиотеку «ngImprovedTesting». Только что выпущенная версия 0.1 поддерживает (выборочно) макетирование зависимостей контроллера, фильтра или другого сервиса.

Смоделировать зависимость «userService» при тестировании «missionsService »теперь чрезвычайно просто:

1
2
3
4
5
6
7
8
describe('ngImprovedTesting mocked style permissions service specification',
        function() {
  
    beforeEach(ModuleBuilder.forModule('myApp')
        .serviceWithMocksFor('permissionService', 'userService')
        .build());
  
    // ... continous in next code snippets

Вместо использования традиционного beforeEach (module (‘myApp’)) мы используем ModuleBuilder из ngImprovedTesting, чтобы создать модуль специально для нашего теста. В этом случае мы хотели бы протестировать фактический «allowService» в тесте в сочетании с макетом для его зависимости «userService».

Но что, если я хотел бы установить поведение для автоматически созданного макета …
… как мне на самом деле завладеть фактическим экземпляром?

Что ж, просто… кроме тестируемого компонента, все его зависимости, включая фиктивную, могут быть введены.

Чтобы отличить макет от обычного, он зарегистрирован с пометкой «Mock», добавленной к его названию. Поэтому, чтобы внедрить макетированную версию «userService», просто используйте «userServiceMock»:

1
2
3
4
5
6
7
8
describe('hasAdminAccess method', function() {
      it('should return true when user details has property: admin == true',
              inject(function(permissions, userServiceMock) {
          userServiceMock.getUserDetails.andReturn({admin: true});
 
          expect(permissions.hasAdminAccess('anAdminUser')).toBe(true);
      }));
  });

Как видно из примера, метод «userServiceMock.getUserDetails» — это просто шпион Жасмина. Таким образом, он позволяет вызывать «andReturn», чтобы установить возвращаемое значение метода. Однако он не допускает «andCallThrough», так как шпион не находится на исходном сервисе.

Изучение API ModuleBuilder в ngImprovedTesting

Поскольку я не дошел до написания и создания JSDocs / NGDocs, я вместо этого быстро объясню это здесь.

Для создания экземпляра «ModuleBuilder» используйте его статический метод «forModule».
ModuleBuilder (в версии 0.1) состоит из следующих методов экземпляра:

  • serviceWithMocksFor: регистрирует сервис для тестирования и макетирует указанные зависимости
  • serviceWithMocks: регистрирует сервис для тестирования и макетирует все зависимости
  • serviceWithMocksExcept: регистрирует сервис для тестирования и макетирования зависимостей, за исключением указанного
  • controllerWithMocksFor: регистрирует контроллер для тестирования и макетирует указанные зависимости
  • controllerWithMocks: регистрирует контроллер для тестирования и макетирует все зависимости
  • controllerWithMocksExcept: регистрирует контроллер для тестирования и макетов зависимостей, за исключением указанного
  • controllerAsIs: регистрирует контроллер, чтобы его можно было создать с помощью $ controller
  • filterWithMocksFor: регистрирует фильтр для тестирования и макетирует указанные зависимости
  • filterWithMocks: регистрирует фильтр для тестирования и макетирует все зависимости
  • filterWithMocksExcept: регистрирует фильтр для проверки и макетирования зависимостей, за исключением указанного
  • filterAsIs: регистрирует фильтр, так что его можно использовать через $ filter

Ограничения в начальном (0.1) ngImprovedTesting

Хотя версия 0.1 вполне готова для производства (и хорошо протестирована), у нее есть свои ограничения:

  • Услуги, зарегистрированные по методу «провайдер», в настоящее время не могут использоваться в качестве тестируемой услуги; Это означает, что его нельзя использовать в качестве первого параметра «serviceWithMocks…», однако его можно использовать как (потенциально смоделированную) зависимость.
  • Сервисы, которые зарегистрированы с использованием «$ Обеспечить» (то есть внутри функции конфигурации модуля) вместо «angular.Module», не могут использоваться в качестве тестируемого сервиса.
  • Пробное тестирование директив в настоящее время не поддерживается.

Как начать работу с ngImprovedTesting

Все источники из этого блога можно найти как часть примера приложения:

Примеры приложений демонстрируют три различных варианта тестирования:

  • Тот, который использует $ httpBackend
  • Другой, использующий ванильную поддержку
  • И один с помощью ngImprovedTesting

Для выполнения тестов в командной строке используйте следующие команды (требуется установить NodeJS, NPM, Bower и Grunt):

1
2
3
npm install
bower update
grunt

Фактические источники самого ngImprovedTesting также размещены на GitHub:

Кроме того, ngImprovedTesting также доступен через Bower. Вы можете легко установить и добавить его в существующий проект, используя следующую команду:

1
bower install ng-improved-testing --save-dev

Ваш отзыв более чем приветствуется

Моя цель для ngImprovedTesting — облегчить фиктивное тестирование в ваших модульных тестах AngularJS.
Мне очень интересны ваши отзывы … полезен ли ngImprovedTesting … и как его можно улучшить?

Ссылка: ngImprovedTesting: фиктивное тестирование AngularJS стало проще от нашего партнера по JCG Эмиля ван Галена в блоге JDriven .