LPgenerator — профессиональная Landing Page платформа для увеличения продаж вашего бизнеса

  • Более 500 шаблонов в галерее
  • Инструменты оптимизации конверсии
  • Статистика и сквозная аналитика
  • CRM для работы с заявками и телефония
  • Визуальный редактор с расширенным функционалом
  • Быстрая техническая поддержка
  • Множество интеграций
  • Окупаемость инструмента — от 7 дней

AngularJS тестирование – Bootstrap блоки, маршруты, события и анимации

1

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

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

К счастью, AngularJS облегчает тестирование кода, с использованием фреймворка, при помощи таких функций, как Dependency Injection (DI). Эта статья расскажет о тестировании Bootstrap блоков приложения AngularJS (включает блоки конфигурации, блоки запуска и resolve блоки маршрутов), системе событий и анимации.

Тестирование блоков конфигурации и запуска

Блоки конфигурации и запуска выполняются в начале жизненного цикла модуля. Они содержат важную логику, которая управляет тем, как работает модуль, виджет или приложение. Немного сложно проверить их, поскольку они не могут быть тотчас вызваны, как другие компоненты. В то же время, они не могут быть проигнорированы, так как они играют ключевую роль.

Рассмотрим следующие блоки конфигурации и запуска:

angular.module('configAndRunBlocks', ['ngRoute'])
    .config(function ($routeProvider) {
    $routeProvider.when('/home', {
        templateUrl: 'home.html',
        controller: 'HomeController',
        resolve: {
            bootstrap: ['$q', function ($q) {
                return $q.when({
                    prop: 'value'
                });
            }]
        }
    })
        .when('/details/:id', {
        templateUrl: 'details.html',
        controller: 'DetailsController'
    })
        .otherwise({
        redirectTo: '/home'
    });
})
    .run(function ($rootScope, messenger) {
 
    messenger.send('Bootstrapping application');
    $rootScope.$on('$locationChangeStart', function (event, next, current) {
        messenger.send('Changing route to ' + next + ' from ' + current);
    });
});

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

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

describe('config and run blocks', function () {
    var routeProvider, messenger;
 
    beforeEach(function () {
        module('ngRoute');
 
        module(function ($provide, $routeProvider) {
            routeProvider = $routeProvider;
            spyOn(routeProvider, 'when').andCallThrough();
            spyOn(routeProvider, 'otherwise').andCallThrough();
 
            messenger = {
                send: jasmine.createSpy('send')
            };
            $provide.value('messenger', messenger);
        });
 
        module('configAndRunBlocks');
    });
 
    beforeEach(inject());
});

Я намеренно не моделировал объект $routeProvider, так как мы протестируем зарегистрированные маршруты позже в этой статье.

Теперь, когда модуль загружен, блоки конфигурации и запуска уже были выполнены. Таким образом, мы можем начать тестировать их поведение. Так как блок конфигурации регистрирует маршруты, мы можем проверить, зарегистрировал ли он правильные маршруты. Мы проверим, зарегистрировано ли ожидаемое количество маршрутов. Следующие тесты проверяют работоспособность блока конфигурации:

describe('config block tests', function () {
    it('should have called registered 2 routes', function () {
        //Otherwise internally calls when. So, call count of when has to be 3
        expect(routeProvider.when.callCount).toBe(3);
    });
 
    it('should have registered a default route', function () {
        expect(routeProvider.otherwise).toHaveBeenCalled();
    });
});

Блок запуска в образце кода вызывает службу и регистрирует событие. Мы протестируем событие позже в этой статье. На данный момент, давайте проверим вызов метода службы:

describe('run block tests', function () {
    var rootScope;
    beforeEach(inject(function ($rootScope) {
        rootScope = $rootScope;
    }));
    it('should send application bootstrap message', function () {
        expect(messenger.send).toHaveBeenCalled();
        expect(messenger.send).toHaveBeenCalledWith("Bootstrapping application");
    });
});

Тестирование системы событий

Событие скопления является одним из хороших способов того, чтобы два объекта взаимодействовали друг с другом, даже если они совершенно не знают друг друга. AngularJS обеспечивает эту функцию через события $emit/$broadcast на $scope. Любой объект в приложении может вызвать событие или слушать событие, в зависимости от необходимости.

При запуске приложения доступны и подписчик, и издатель событий. Но, так как модульные тесты написаны в изоляции, мы имеем только один из объектов, доступный в модульных тестах. Так, спецификации теста будут имитировать другой конец, чтобы иметь возможность проверить работоспособность.

Давайте протестируем событие, зарегистрированное в блоке запуска выше:

$rootScope.$on('$locationChangeStart', function (event, next, current) {
    messenger.send('Changing route to ' + next + ' from ' + current);
});

Событие $locationChangeStart транслируется службой $location всякий раз, когда меняется расположение приложения. Как уже упоминалось, мы должны вручную запустить это событие и протестировать, отправляется ли сообщение отправителем. Следующий тест выполняет эту задачу:

it('should handle the $locationChangeStart event', function () {
    var next = '/second';
    var current = '/first';
    rootScope.$broadcast('$locationChangeStart', next, current);
    expect(messenger.send).toHaveBeenCalled();
    expect(messenger.send).toHaveBeenCalledWith('Changing route to ' + next + ' from ' + current);
});

Тестирование маршрутов

Маршруты определяют способ навигации приложения пользователями. Любое неправильное или случайное изменение конфигурации маршрутов приведет к плохому ux. Так, у маршрутов тоже должны быть тесты.

Пока ngRoute и ui-router являются наиболее широко используемыми маршрутами в приложениях AngularJS. Маршруты для обоих из этих провайдеров должны быть определены в блоке конфигурации, в то время как данные маршрута доступны через службы. Данные маршрута, конфигурированные ngRoute, доступны через сервис $route. Данные маршрута ui-router доступны через сервис $state. Эти услуги могут быть использованы для проверки, правильно ли конфигурирован набор маршрутов.

Рассмотрим следующий блок конфигурации:

angular.module('configAndRunBlocks', ['ngRoute'])
    .config(function ($routeProvider) {
    $routeProvider.when('/home', {
        templateUrl: 'home.html',
        controller: 'HomeController',
        resolve: {
            bootstrap: ['$q', function ($q) {
                return $q.when({
                    prop: 'value'
                });
            }]
        }
    })
        .when('/details/:id', {
        templateUrl: 'details.html',
        controller: 'DetailsController'
    })
        .otherwise({
        redirectTo: '/home'
    });
});

Давайте теперь протестируем эти маршруты. В первую очередь, давайте получим ссылку службы $route:

beforeEach(inject(function ($route) {
    route = $route;
}));

Маршрут /home выше, имеет templateUrl, контроллер и конфигурированный resolve блок. Давайте напишем утверждения, чтобы протестировать их:

it('should have home route with right template, controller and a resolve block', function () {
    var homeRoute = route.routes['/home'];
    expect(homeRoute).toBeDefined();
    expect(homeRoute.controller).toEqual('HomeController');
    expect(homeRoute.templateUrl).toEqual('home.html');
    expect(homeRoute.resolve.bootstrap).toBeDefined();
});

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

it('should have a default route', function () {
    var defaultRoute = route.routes['null'];
    expect(defaultRoute).toBeDefined();
});

Тестирование resolve блоков

Resolve блоки – заводы, которые создаются, когда маршрут будет загружен и доступны контроллеру, связанного с маршрутом. Это интересный сценарий для тестирования, так как их возможности ограничены, и нам по-прежнему необходимо получить ссылку на объект.

Единственный способ проверить resolve блок состоит в применении его с помощью сервиса $ инжектора $injector. После вызова, он может быть протестирован. Следующий фрагмент проверяет resolve блок, конфигурированный домашним маршрутом, который мы создали выше:
 

it('should return data on calling the resolve block', function () {
    var homeRoute = route.routes['/home'];
    var bootstrapResolveBlock = homeRoute.resolve.bootstrap;
    httpBackend.expectGET('home.html').respond('<div>This is the homepage!</div>');
    var bootstrapSvc = injector.invoke(bootstrapResolveBlock); //[1].call(q);
    bootstrapSvc.then(function (data) {
        expect(data).toEqual({
            prop: 'value'
        });
    });
    rootScope.$digest();
    httpBackend.flush();
});

Мне пришлось имитировать templateUrl в вышеупомянутом тесте, так как AngularJS пытается перейти на маршрут по умолчанию, когда цикл дайджест вызывается.

Такой же подход может быть использован для тестирования $httpInterceptors.

Тестирование Анимации

Методика тестирования анимации имеет некоторое сходство с тестированием директив, но тестирование анимации проще, так как анимация не так сложна, как директивы.

Библиотека angular-макетов содержит модуль ngAnimateMock, чтобы облегчить работу тестирования анимации. Этот модуль должен быть загружен перед тестированием анимации.

Рассмотрим следующую JavaScript анимацию:

angular.module('animationsApp', ['ngAnimate']).animation('.view-slide-in', function () {
    return {
        enter: function (element, done) {
            element.css({
                opacity: 0.5,
                position: "relative",
                top: "10px",
                left: "20px"
            })
                .animate({
                top: 0,
                left: 0,
                opacity: 1
            }, 500, done);
        },
        leave: function (element, done) {
            element.animate({
                opacity: 0.5,
                top: "10px",
                left: "20px"
            }, 100, done);
        }
    };
});

Давайте теперь напишем тесты для проверки точности этой анимации. Мы должны загрузить необходимые модули и получить ссылки на необходимые объекты.

beforeEach(function () {
    module('ngAnimate', 'ngAnimateMock', 'animationsApp');
    inject(function ($animate, $rootScope, $rootElement) {
        $animate.enabled(true);
        animate = $animate;
        rootScope = $rootScope;
        rootElement = $rootElement;
        divElement = angular.element('<div class="view-slide-in">This is my view</div>');
        rootScope.$digest();
    });
});

Чтобы проверить вводную часть анимации, определенную выше, мы должны программно ввести элемнет в rootElement, на который сослались в приведенном фрагменте.

Важно помнить перед проверкой анимации, что AngularJS мешает анимации запуститься до того, пока не завершится первый цикл дайджеста. Это делается, чтобы ускорить первоначальное связывание. Последнее утверждение в приведенном фрагменте начинает первый дайджест цикл так, что мы не должны делать это в каждом тесте.

Давайте протестируем вводную анимацию, обозначенную выше. Она имеет два случая теста:

Элемент должен располагаться на 10px сверху 20px слева с непрозрачностью 0,5 при вводе
Элемент должен располагаться на 0px сверху 0px слева с непрозрачностью 1, после 1 секунды ввода. Это должно быть асинхронным тестом, так как контролю придется ждать в течение 1 секунды, прежде чем утверждать.
Ниже приведены тесты для указанных двух случаев:
 

it('element should start entering from bottom right', function () {
    animate.enter(divElement, rootElement);
    rootScope.$digest();
 
    expect(divElement.css('opacity')).toEqual('0.5');
    expect(divElement.css('position')).toEqual('relative');
    expect(divElement.css('top')).toEqual('10px');
    expect(divElement.css('left')).toEqual('20px');
});
 
it('element should be positioned after 1 sec', function (done) {
    animate.enter(divElement, rootElement);
    rootScope.$digest();
 
    setTimeout(function () {
        expect(divElement.css('opacity')).toEqual('1');
        expect(divElement.css('position')).toEqual('relative');
        expect(divElement.css('top')).toEqual('0px');
        expect(divElement.css('left')).toEqual('0px');
        done();
    }, 1000);
});

Аналогично, для конечной анимации нам нужно проверить значения свойств CSS после 100 мс. Так тест должен ждать завершения анимации, мы должны сделать тест асинхронным.

it('element should leave by sliding towards bottom right for 100ms', function (done) {
    rootElement.append(divElement);
    animate.leave(divElement, rootElement);
    rootScope.$digest();
    setTimeout(function () {
        expect(divElement.css('opacity')).toEqual('0.5');
        expect(divElement.css('top')).toEqual('10px');
        expect(divElement.css('left')).toEqual('20px');
        done();
    }, 105);
    //5 ms delay in the above snippet is to include some time for the digest cycle
});

Вывод

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

Высоких конверсий!

31 марта 2016

LPgenerator — профессиональная Landing Page платформа для увеличения продаж вашего бизнеса

  • Более 500 шаблонов в галерее
  • Инструменты оптимизации конверсии
  • Статистика и сквозная аналитика
  • CRM для работы с заявками и телефония
  • Визуальный редактор с расширенным функционалом
  • Быстрая техническая поддержка
  • Множество интеграций
  • Окупаемость инструмента — от 7 дней
blog comments powered by Disqus
copyright © 2011–2017 by LPgenerator LLC. Все права защищены
Запрещено любое копирование материалов ресурса без письменного согласия владельца — ООО "ЛПгенератор".