Эта статья является второй в серии об изучении AngularJS . Он описывает, как протестировать простое приложение AngularJS. В предыдущей статье « Начало работы с AngularJS» я показал, как разработать простую функцию поиска и редактирования.
Что вы узнаете
Вы научитесь использовать Jasmine для модульного тестирования контроллеров и Protractor для интеграционного тестирования. В документации Angular есть хорошее руководство для разработчиков по модульному тестированию, если вам нужна дополнительная информация о тестировании и почему это важно.
Лучшая причина для написания тестов — автоматизировать тестирование. Без тестов вы, скорее всего, будете тестировать вручную. Это ручное тестирование будет занимать все больше и больше времени по мере роста вашей кодовой базы.
Что вам нужно
- Около 15-30 минут
- Любимый текстовый редактор или IDE. Мы рекомендуем IntelliJ IDEA .
- Git установлен.
- Node.js и NPM установлены.
Получить учебный проект
Клонируйте репозиторий angular-tutorial с помощью git и установите зависимости.
git clone https://github.com/mraible/angular-tutorial.git cd angular-tutorial npm install
Если вы еще не завершили учебное пособие « Начало работы с AngularJS» , вам следует внимательно изучить его, чтобы понять, как работает это приложение. Вы также можете просто запустить приложение с помощью «npm start» и просмотреть его в своем браузере по адресу http: // localhost: 8000 / app / .
Проверьте SearchController
-
Создайте
app/search/search_test.js
и заполните его базовой тестовой инфраструктурой. Этот код устанавливает макет ,SearchService
который имеет первую функцию мы хотим проверить:query(term)
. Используется$provide
для переопределения по умолчаниюSearchService
. Контроллеры угловых юнит-тестов — служба имитации внутри контроллера была полезным вопросом для Stack Overflow, чтобы выяснить, как имитировать сервисы в контроллерах.'use strict'; describe('myApp.search module', function() { var mockSearchService; beforeEach(module('myApp.search', function($provide) { mockSearchService = {query: function(term) {}}; $provide.value("SearchService", mockSearchService); })); });
-
Измените
karma.conf.js
(в корневом каталоге), чтобы добавить реализацию поиска и проверить.files : [ ... 'app/components/**/*.js', 'app/search/search.js', 'app/search/search_test.js', 'app/view*/**/*.js' ],
-
Добавьте первый тест в
search_test.js
. Этот тест проверяет, что установка условия поиска и выполнениеsearch()
функции вызовет службу и вернет результаты. Вы можете видеть, что результаты, возвращенные службой, высмеиваютсяdeferred.resolve()
.deferred.resolve()
Вызов , как обрабатывать обещания модульных тестов . Связанный: Введение в модульное тестирование: Шпионы — хорошее введение в использование шпионов в модульных тестах.describe('search by term', function() { var scope, rootScope, controller, deferred; // setup the controller with dependencies. beforeEach(inject(function($rootScope, $controller, $q) { rootScope = $rootScope; scope = $rootScope.$new(); controller = $controller('SearchController', {$scope: scope, SearchService: mockSearchService }); deferred = $q.defer(); })); it('should search when a term is set and search() is called', function() { spyOn(mockSearchService, 'query').andReturn(deferred.promise); scope.term = 'M'; scope.search(); deferred.resolve({data: {name: "Peyton Manning"}}); rootScope.$apply(); expect(scope.searchResults).toEqual({name: "Peyton Manning"}); }); });
-
Запустите следующую команду в командной строке, чтобы запустить тестер кармы. Вы можете оставить этот процесс запущенным, и новые тесты будут запускаться автоматически. Вы можете изменить проверенные данные и ожидания, чтобы увидеть, что ваш тест не пройден.
npm test
Запуск тестов из IntelliJ IDEAСм.
Раздел «Запуск модульных тестов на Karma», чтобы узнать, как запускать тесты из IntelliJ IDEA.
-
Добавьте тест, чтобы убедиться, что поиск происходит автоматически, когда термин входит в URL. Обратите внимание, как структура кода должна была немного измениться для обработки
$routeParams
.describe('search by term automatically', function() { var scope, rootScope, controller, location, deferred; beforeEach(inject(function($rootScope, $controller, $q) { rootScope = $rootScope; scope = $rootScope.$new(); // in this case, expectations need to be setup before controller is initialized var routeParams = {"term": "peyton"}; deferred = $q.defer(); spyOn(mockSearchService, 'query').andReturn(deferred.promise); deferred.resolve({data: {name: "Peyton Manning"}}); controller = $controller('SearchController', {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService }); })); it('should search automatically when a term is on the URL', function() { rootScope.$apply(); expect(scope.searchResults).toEqual({name: "Peyton Manning"}); }); });
-
Добавьте тест для проверки
EditController
работ, как ожидалось.describe('edit person', function() { var scope, rootScope, controller, location, deferred; beforeEach(inject(function($rootScope, $controller, $q) { rootScope = $rootScope; scope = $rootScope.$new(); // expectations need to be setup before controller is initialized var routeParams = {"id": "1"}; deferred = $q.defer(); spyOn(mockSearchService, 'fetch').andReturn(deferred.promise); deferred.resolve({data: {name: "Peyton Manning", address: {street: "12345 Blake Street", city: "Denver"}}}); controller = $controller('EditController', {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService }); })); it('should fetch a single record', function() { rootScope.$apply(); expect(scope.person.name).toBe("Peyton Manning"); expect(scope.person.address.street).toBe("12345 Blake Street"); }); });
После добавления этого теста вы, вероятно, увидите следующую ошибку в своем терминале.
Chrome 40.0.2214 (Mac OS X 10.10.2) myApp.search module edit person should fetch a single record FAILED fetch() method does not exist TypeError: Cannot read property 'name' of undefined
Это происходит потому, что
mockSearchService
не определен метод выборки. ИзменитеbeforeEach()
строку 7, чтобы добавить эту функцию.mockSearchService = {query: function(term) {}, fetch: function(id) {}};
Дополнительный кредит
Создать тест для спасения человека. Вот вопрос о переполнении стека, который может помочь вам проверить местоположение после выполнения сохранения.
Проверьте функцию поиска
Чтобы проверить, работает ли приложение полностью, вы можете написать сценарии с помощью Protractor . Они также известны как интеграционные тесты, так как они тестируют интеграцию между всеми уровнями вашего приложения.
Чтобы проверить работу сквозных тестов в проекте перед началом, выполните следующую команду в одном окне терминала:
npm start
Затем в другом окне выполните следующую команду, чтобы выполнить тесты:
npm run protractor
1. Напишите свой первый интеграционный тест, чтобы убедиться, что вы можете перейти к / search и ввести поисковый запрос, чтобы увидеть результаты. Добавьте следующее к e2e-tests/scenarios.js
:
describe('search', function() { var searchTerm = element(by.model('term')); var searchButton = element(by.id('search')); beforeEach(function() { browser.get('index.html#/search'); }); it('should allow searching at /search', function() { searchTerm.sendKeys("M"); searchButton.click().then(function() { expect(element.all(by.repeater('person in searchResults')).count()).toEqual(3); }); }); });
Переменная searchTerm представляет поле ввода. В by.model()
синтаксических связывается с атрибутом «нг-модели» вы определили в HTML.
2. Запустите «Запуск транспортира npm». Это должно произойти со следующей ошибкой.
[launcher] Running 1 instances of WebDriver Selenium standalone server started at http://172.16.6.39:64230/wd/hub ...F Failures: 1) my app search should allow searching at /search Message: NoSuchElementError: No element found using locator: By.id("search")
3. Для исправления необходимо добавить атрибут «id» к кнопке «Поиск» в app/search/index.html
.
<form ng-submit="search()"> <input type="search" name="search" ng-model="term"> <button id="search">Search</button> </form>
4. Запустите тесты снова, используя «npm run protractor». Они все должны пройти это время.
5. Напишите еще один тест, чтобы убедиться, что редактирование пользователя отображает его информацию.
describe('edit person', function() { var name = element(by.model('person.name')); var street = element(by.model('person.address.street')); var city = element(by.model('person.address.city')); beforeEach(function() { browser.get('index.html#/edit/1'); }); it('should allow viewing a person', function() { // getText() doesn't work with input elements, see the following for more information: // https://github.com/angular/protractor/blob/master/docs/faq.md#the-result-of-gettext-from-an-input-element-is-always-empty expect(name.getAttribute('value')).toEqual("Peyton Manning"); expect(street.getAttribute('value')).toEqual("1234 Main Street"); expect(city.getAttribute('value')).toEqual("Greenwood Village"); }); });
Убедитесь, что он работает с «npm run protractor».
6. Наконец, напишите тест, чтобы убедиться, что вы можете сохранить человека, и его данные обновляются. Эта проблема помогла выяснить, как проверить URL-адрес после его изменения .
describe('save person', function() { var name = element(by.model('person.name')); var save = element(by.id('save')); beforeEach(function() { browser.get('index.html#/edit/1'); }); it('should allow updating a name', function() { name.sendKeys(" Updated"); save.click().then(function() { // verify url set back to search results browser.driver.wait(function() { return browser.driver.getCurrentUrl().then(function(url) { expect(url).toContain('/search/Peyton%20Manning%20Updated'); return url; }); }); // verify one element matched this change expect(element.all(by.repeater('person in searchResults')).count()).toEqual(1); }); }); });
7. Когда вы запускаете этот тест с «npm run protractor», он должен завершиться неудачей, потому что нет элемента с id="save"
in app/search/edit.html
. Добавьте его к кнопке Сохранить в этом файле и повторите попытку. Вы должны увидеть что-то похожее на следующее:
Finished in 4.478 seconds 6 tests, 9 assertions, 0 failures
Исходный код
Завершенный проект с этим кодом доступен на GitHub по адресу https://github.com/mraible/angular-tutorial в разделе тестирования .
Есть два коммита, которые вносят изменения для двух основных шагов в этом уроке:
Резюме
Надеюсь, вам понравился этот быстрый и легкий учебник по тестированию приложений AngularJS. Первая пара приложений AngularJS, которые я разработал, не имела тестов и требовала много ручного тестирования, чтобы проверить их качество. Узнав, что тестировать приложения AngularJS довольно легко, я теперь делаю это на всех своих проектах. Надеюсь, этот урок мотивирует вас сделать то же самое.