Статьи

Тестирование углового приложения: Angular JS и Spring Security. Часть VIII.

Первоначально написано Дейв Сайер в блоге Spring

В этой статье мы продолжим  наше обсуждение  того, как использовать  Spring Security  с  Angular JS  в «одностраничном приложении». Здесь мы покажем, как писать и запускать модульные тесты для клиентского кода, используя среду тестирования Javascript  Jasmine . Это восьмая из серии статей, и вы можете узнать основные строительные блоки приложения или создать его с нуля, прочитав  первую статью , или просто перейти к  исходному коду в Github. (тот же исходный код, что и в части I, но с добавленными тестами). В этой статье на самом деле очень мало кода, использующего Spring или Spring Security, но в нем описывается тестирование на стороне клиента таким образом, что его не так просто найти в обычных ресурсах сообщества Javascript, и тот, который мы считаем удобным для большинства пользователей Spring.

Как и в остальной части этой серии, инструменты сборки типичны для пользователей Spring, а не для опытных разработчиков. Таким образом, мы ищем решения, которые можно использовать из Java IDE и из командной строки с помощью знакомых инструментов сборки Java. Если вы уже знаете о тестировании Jasmine и Javascript и вам нравится использовать набор инструментов на основе Node.js (например  npmgruntи т. Д.), То вы, вероятно, можете полностью пропустить эту статью. Если вы чувствуете себя более комфортно в Eclipse или IntelliJ и предпочитаете использовать те же инструменты для своего внешнего интерфейса, что и для внутреннего, то эта статья будет интересна. Когда нам нужна командная строка (например, для непрерывной интеграции), мы используем Maven в примерах здесь, но пользователи Gradle, вероятно, найдут тот же код простым для интеграции.

Напоминание: если вы работаете в этом разделе с примером приложения, обязательно очистите кэш браузера от файлов cookie и учетных данных HTTP Basic. В Chrome лучший способ сделать это для отдельного сервера — это открыть новое окно инкогнито.

Написание спецификации в жасмине

Наш «домашний» контроллер в «базовом» приложении очень прост, поэтому его тщательное тестирование не займет много времени. Вот напоминание о коде ( hello.js):

angular.module('hello', []).controller('home', function($scope, $http) {
  $http.get('resource/').success(function(data) {
    $scope.greeting = data;
  })
});

Основная задача , которую мы лицо , чтобы обеспечить  $scope и  $http объекты в тесте, так что мы можем сделать утверждение о том , как они используются в контроллере. На самом деле, даже до того, как мы столкнемся с этой проблемой, нам нужно создать экземпляр контроллера, чтобы мы могли проверить, что происходит при его загрузке. Вот как вы можете это сделать.

Создайте новый файл  spec.js и поместите его в «src / test / resources / static / js»:

describe("App", function() {

beforeEach(module('hello'));

    var $controller;
beforeEach(inject(function($injector) {
$controller = $injector.get('$controller');
}));

it("loads a controller", function() {
var controller = $controller('home')
});

}

В этом очень простом наборе тестов у нас есть 3 важных элемента:

  1. Мы  describe() то, что тестируется («приложение» в данном случае) с функцией.
  2. Внутри этой функции мы предоставляем пару  beforeEach() обратных вызовов, один из которых загружает модуль Angular «привет», а другой создает фабрику для контроллеров, которую мы называем  $controller.
  3. Поведение выражается через призыв к  it(), где мы формулируем словами, что такое ожидание, а затем предоставляем функцию, которая делает утверждения.

Тестовая функция здесь настолько тривиальна, что фактически даже не делает утверждений, но создает экземпляр «домашнего» контроллера, поэтому, если это не удастся, тогда тест не пройден.

ПРИМЕЧАНИЕ: «src / test / resources / static / js» — это логическое место для тестового кода в приложении Java, хотя можно было бы привести случай с «src / test / javascript». Позже мы увидим, почему имеет смысл поместить его в тестовый путь к классам, хотя (действительно, если вы привыкли к соглашениям Spring Boot, вы уже можете понять, почему).

Теперь нам нужен драйвер для этого кода Javascript в форме HTML-страницы, которую мы можем загрузить в браузер. Создайте файл с именем «test.html» и поместите его в «src / test / resources / static»:

<!doctype html>
<html>
<head>

<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css"
  href="/webjars/jasmine/2.0.0/jasmine.css">
<script type="text/javascript" src="/webjars/jasmine/2.0.0/jasmine.js"></script>
<script type="text/javascript"
  src="/webjars/jasmine/2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="/webjars/jasmine/2.0.0/boot.js"></script>

<!-- include source files here... -->
<script type="text/javascript" src="/js/angular-bootstrap.js"></script>
<script type="text/javascript" src="/js/hello.js"></script>

<!-- include spec files here... -->
<script type="text/javascript"
  src="/webjars/angularjs/1.3.8/angular-mocks.js"></script>
<script type="text/javascript" src="/js/spec.js"></script>

</head>

<body>
</body>
</html>

HTML не содержит содержимого, но он загружает некоторый Javascript и будет иметь пользовательский интерфейс после запуска всех сценариев.

Сначала мы загружаем необходимые компоненты жасмина из  /webjars/**. 4 файла, которые мы загружаем, просто шаблонные — вы можете сделать то же самое для любого приложения. Чтобы сделать их доступными во время выполнения в тесте, нам нужно добавить зависимость Jasmine в наш «pom.xml»:

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>jasmine</artifactId>
  <version>2.0.0</version>
  <scope>test</scope>
</dependency>

Затем мы подходим к конкретному коду приложения.
Основным исходным кодом для нашего интерфейса является «hello.js», поэтому мы должны загрузить его, а также его зависимости в виде «angular-bootstrap.js» (последний создается плагином wro4j maven, поэтому вам нужно запустить `mvn package` один раз, прежде чем он будет загружен).

Наконец, нам нужен «spec.js», который мы написали jsut, и его зависимости (любые, которые еще не включены в другие скрипты), которые для приложения Angular почти всегда будут включать «angular-mocks.js».
Мы загружаем его из веб-файлов, поэтому вам также необходимо добавить эту зависимость в «pom.xml»:

org.webjars
angularjs
1.3.8
test

« `

ПРИМЕЧАНИЕ: webjar angularjs уже был включен как плагин wro4j, чтобы он мог создать «angular-bootstrap.js». Это будет использоваться на другом этапе сборки, поэтому нам это нужно снова.

Запуск спецификации

Для запуска нашего кода «test.html» нам нужно крошечное приложение (например, в «src / test / java / test»):

@SpringBootApplication
@Controller
public class TestApplication {

@RequestMapping("/")
public String home() {
return "forward:/test.html";
}

public static void main(String[] args) {
new SpringApplicationBuilder(TestApplication.class).properties(
"server.port=9999", "security.basic.enabled=false").run(args);
}

}

Это  TestApplication чистый пример: все приложения могут запускать тесты одинаково. Вы можете запустить его в своей IDE и посетить  http: // localhost: 9999,  чтобы увидеть, как работает Javascript. Тот, который @RequestMapping мы предоставили, просто заставляет домашнюю страницу отображать тестовый HTML. Все (один) тесты должны быть зелеными.

Отсюда ваш рабочий процесс для разработчиков — внести изменения в код Javascript и перезагрузить тестовое приложение в браузере для запуска тестов. Так просто!

Улучшение модульного теста: Mocking HTTP Backend

Чтобы улучшить спецификацию до уровня производительности, нам нужно кое-что сказать о том, что происходит при загрузке контроллера. Поскольку он выполняет вызов,  $http.get() нам нужно смоделировать этот вызов, чтобы избежать запуска всего приложения только для модульного теста. Для этого мы используем Angular  $httpBackend (в «spec.js»):

describe("App", function() {

  beforeEach(module('hello'));

  var $httpBackend, $controller;
  beforeEach(inject(function($injector) {
    $httpBackend = $injector.get('$httpBackend');
    $controller = $injector.get('$controller');
  }));

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

  it("says Hello Test when controller loads", function() {
    var $scope = {};
    $httpBackend.expectGET('resource/').respond(200, {
      id : 4321,
      content : 'Hello Test'
    });
    var controller = $controller('home', {
      $scope : $scope
    });
    $httpBackend.flush();
    expect($scope.greeting.content).toEqual('Hello Test');
  });

})

Новые части здесь:

  • Создание  $httpBackend в  beforeEach().
  • Добавление нового,  afterEach() который проверяет состояние бэкэнда.
  • В тестовой функции мы устанавливаем ожидания для бэкэнда перед тем, как создать контроллер, сообщая ему, что он должен ожидать вызова ‘resource /’, и каков должен быть ответ.
  • Мы также добавляем призыв к Жасмин,  expect() чтобы утверждать результат.

Без запуска и остановки тестового приложения этот тест теперь должен быть зеленым в браузере.

Запуск спецификации в командной строке

Замечательно иметь возможность запускать спецификации в браузере, потому что в современные браузеры встроены отличные инструменты разработчика (например, F12 в Chrome). Вы можете устанавливать точки останова и проверять переменные, а также иметь возможность обновлять представление для повторного запуска ваших тестов на работающем сервере. Но это не поможет вам с непрерывной интеграцией: для этого вам нужен способ запуска тестов из командной строки. Доступны инструменты для любых инструментов сборки, которые вы предпочитаете использовать, но так как мы используем Maven здесь, мы добавим плагин в «pom.xml»:

<plugin>
  <groupId>com.github.searls</groupId>
  <artifactId>jasmine-maven-plugin</artifactId>
  <version>2.0-alpha-01</version>
  <executions>
    <execution>
      <goals>
        <goal>test</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Настройки по умолчанию для этого плагина не будут работать с статической компоновкой ресурсов, которую мы уже сделали, поэтому нам понадобится немного конфигурации для этого:

<plugin>
  ...
  <configuration>
    <additionalContexts>
      <context>
        <contextRoot>/lib</contextRoot>
        <directory>${project.build.directory}/generated-resources/static/js</directory>
      </context>
    </additionalContexts>
    <preloadSources>
      <source>/lib/angular-bootstrap.js</source>
      <source>/webjars/angularjs/1.3.8/angular-mocks.js</source>
    </preloadSources>
    <jsSrcDir>${project.basedir}/src/main/resources/static/js</jsSrcDir>
    <jsTestSrcDir>${project.basedir}/src/test/resources/static/js</jsTestSrcDir>
    <webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
  </configuration>
</plugin>

Обратите внимание, что  webDriverClassName указан как  PhantomJSDriver, что означает, что вы должны  phantomjs быть на вашем  PATH во время выполнения. Это работает из коробки в  Travis CI и требует простой установки в Linux, MacOS и Windows — вы можете  скачать двоичные файлы  или использовать менеджер пакетов, как, например,  apt-get в Ubuntu. В принципе, здесь можно использовать любой веб-драйвер Selenium (по умолчанию  HtmlUnitDriver), но PhantomJS, вероятно, является лучшим для использования в приложениях Angular.

Нам также нужно сделать библиотеку Angular доступной для плагина, чтобы он мог загрузить эту зависимость «angular-mocks.js»

<plugin>
  ...
  <dependencies>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>angularjs</artifactId>
      <version>1.3.8</version>
    </dependency>
  </dependencies>
</plugin>

Вот и все. Все опять шаблонно (так что это может войти в родительский pom, если вы хотите разделить код между несколькими проектами). Просто запустите его в командной строке:

$ mvn jasmine:test

Тесты также выполняются как часть жизненного цикла «тестов» Maven, так что вы можете просто запустить их  mvn test для запуска всех тестов Java, а также Javascript, очень плавно вписываясь в существующий цикл сборки и развертывания. Вот журнал:

$ mvn test
...
[INFO] 
-------------------------------------------------------
 J A S M I N E   S P E C S
-------------------------------------------------------
[INFO] 
App
  says Hello Test when controller loads

Results: 1 specs, 0 failures

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.064s
[INFO] Finished at: Sun Apr 26 14:46:14 BST 2015
[INFO] Final Memory: 47M/385M
[INFO] ------------------------------------------------------------------------

Плагин Jasmine Maven также поставляется с целью  mvn jasmine:bdd запуска сервера, который вы можете загрузить в своем браузере для запуска тестов (в качестве альтернативы  TestApplicationвышеупомянутому).

Вывод

Возможность запуска модульных тестов для Javascript важна в современном веб-приложении, и это тема, которую мы игнорировали (или уклонялись) до сих пор в этой серии. В этой статье мы представили основные компоненты того, как писать тесты, как их запускать во время разработки, а также, что важно, в условиях непрерывной интеграции. Подход, который мы выбрали, подходит не всем, поэтому, пожалуйста, не расстраивайтесь по-другому, но убедитесь, что у вас есть все эти ингредиенты. То, как мы это сделали здесь, вероятно, будет комфортно для традиционных корпоративных разработчиков Java и хорошо интегрируется с их существующими инструментами и процессами, поэтому, если вы находитесь в этой категории, я надеюсь, что вы найдете это полезным в качестве отправной точки. Больше примеров тестирования с Angular и Jasmine можно найти во многих местах в Интернете,но первая точка вызова может быть «Одиночный» образец  из этой серии, в котором теперь есть некоторый обновленный тестовый код, который немного менее тривиален, чем код, который нам нужно было написать для «базового» образца в этой статье.