Эта статья является второй в серии об изучении 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 довольно легко, я теперь делаю это на всех своих проектах. Надеюсь, этот урок мотивирует вас сделать то же самое.