Рубрики
Без рубрики

Протестируйте свои приложения Spring Boot с помощью JUnit 5

Узнайте, как использовать JUnit 5 для написания модульных и интеграционных тестов на основе Java для ваших приложений Spring Boot. Помечено как java, тестирование, junit, учебное пособие.

В этом посте вы расскажете, как создать простое приложение Spring Boot и протестировать его с помощью Junit 5. Приложение без тестирования – это пресловутый ящик Пандоры. Что хорошего в вашем приложении, если вы не знаете, что оно будет работать при любых условиях? Добавление набора тестов повышает уверенность в том, что ваше приложение сможет справиться со всем, что ему будет брошено. При создании тестов важно использовать современный и всеобъемлющий набор инструментов. Использование современной платформы гарантирует, что вы сможете идти в ногу с изменениями в вашем языке и библиотеках. Полный набор инструментов гарантирует, что вы сможете адекватно протестировать все области вашего приложения, не обременяя себя написанием собственных тестовых утилит. JUnit 5 хорошо справляется с обоими требованиями.

Приложение, используемое для этого поста, будет представлять собой базовый REST API с конечными точками для вычисления нескольких данных о дне рождения человека! Есть три конечных точки ЗАПИСИ, которые вы сможете использовать для определения дня недели, астрологического знака или китайского знака Зодиака для дня рождения. Этот REST API будет защищен с помощью OAuth 2.0 и Okta . Как только мы создадим API, мы пройдем модульное тестирование кода с помощью JUnit 5 и рассмотрим охват наших тестов JUnit.

Основным преимуществом использования Spring Framework является возможность внедрения ваших зависимостей, что значительно упрощает замену реализаций для различных целей, но не в последнюю очередь для модульного тестирования. Spring Boot делает это еще проще, позволяя вам выполнять большую часть внедрения зависимостей с помощью аннотаций вместо того, чтобы возиться со сложным applicationContext.xml |/файл!

записка: Для этого поста я буду использовать Eclipse, так как это моя предпочтительная среда разработки. Если вы также используете Eclipse, вам понадобится версия Oxygen или beyond, чтобы включить поддержку тестирования Unit 5 (Jupiter): https://www.eclipse.org/downloads/packages/installer .

Создайте приложение Spring Boot для тестирования с помощью JUnit 5

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

Чтобы приступить к работе, вы создадите проект Spring Boot с нуля.

записка: Следующие шаги предназначены для Eclipse. Если вы используете другую среду IDE, скорее всего, существуют эквивалентные шаги. При желании вы можете создать свою собственную структуру каталогов проекта и написать окончательный pom.xml файл в любом текстовом редакторе, который вам нравится.

Создайте новый проект Maven из Файла > Новое меню. Выберите местоположение вашего нового проекта и дважды нажмите кнопку Далее, а затем заполните идентификатор группы, идентификатор артефакта и версию вашего приложения. Для этого примера я использовал следующие параметры:

  • Идентификатор группы: com.example.joy
  • Идентификатор артефакта: мой Первый Весенний Ботинок
  • Версия: 0.0.1-МОМЕНТАЛЬНЫЙ СНИМОК

ПОДСКАЗКА: Если Maven для вас новый и неясно, как выбрать идентификатор группы, идентификатор артефакта или версию, пожалуйста, ознакомьтесь с этими соглашениями: https://maven.apache.org/guides/mini/guide-naming-conventions.html

Когда это будет сделано, это приведет к появлению pom.xml файл, который выглядит следующим образом:


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.3.RELEASE
    
    com.example.joy
    myFirstSpringBoot
    0.0.1-SNAPSHOT


Далее вы захотите обновить pom.xml с некоторыми базовыми настройками и зависимостями, которые будут выглядеть следующим образом (добавьте все после версии):


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.3.RELEASE
    
    com.example.joy
    myFirstSpringBoot
    0.0.1-SNAPSHOT

    
        1.8
        2.1.3.RELEASE
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
           
                
                    junit
                    junit
                
            
        
        
             org.junit.jupiter
             junit-jupiter-engine
             test
         
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


Обратите внимание, что вам необходимо исключить JUnit по умолчанию из зависимости spring-boot-starter-test. Зависимость junit-jupiter-engine предназначена для JUnit 5.

Создайте Java REST API с Spring Boot для тестирования JUnit 5

Давайте начнем с основного файла приложения, который является точкой входа для запуска Java API. Это файл с именем SpringBootRestApiApplication.java это выглядит следующим образом:

package com.example.joy.myFirstSpringBoot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = {"com.example.joy"})
public class SpringBootRestApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRestApiApplication.class, args);
    }
}

Аннотация SpringBootApplication сообщает приложению, что оно должно поддерживать автоматическую настройку, сканирование компонентов (пакета com.example.joy и всего, что находится под ним) и регистрацию компонента.

@SpringBootApplication(scanBasePackages = {"com.example.joy"})

Эта строка запускает приложение REST API:

SpringApplication.run(SpringBootRestApiApplication.class, args);

BirthdayService.java – это интерфейс для службы birthday. Это довольно прямолинейно, определяя, что доступны четыре вспомогательные функции.

package com.example.joy.myFirstSpringBoot.services;

import java.time.LocalDate;

public interface BirthdayService {
    LocalDate getValidBirthday(String birthdayString) ;

    String getBirthDOW(LocalDate birthday);

    String getChineseZodiac(LocalDate birthday);

    String getStarSign(LocalDate birthday) ;
}

BirthdayInfoController.java обрабатывает три запроса post для получения информации о дне рождения. Это выглядит так:

package com.example.joy.myFirstSpringBoot.controllers;

import java.time.LocalDate;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.joy.myFirstSpringBoot.services.BirthdayService;

@RestController
@RequestMapping("/birthday")
public class BirthdayInfoController {
    private final BirthdayService birthdayService;

    public BirthdayInfoController(BirthdayService birthdayService){
        this.birthdayService = birthdayService;
    }

    @PostMapping("/dayOfWeek")
    public String getDayOfWeek(@RequestBody String birthdayString) {
        LocalDate birthday = birthdayService.getValidBirthday(birthdayString);

        String dow = birthdayService.getBirthDOW(birthday);

        return dow;
    }

    @PostMapping("/chineseZodiac")
    public String getChineseZodiac(@RequestBody String birthdayString) {
        LocalDate birthday = birthdayService.getValidBirthday(birthdayString);
        String sign = birthdayService.getChineseZodiac(birthday);

        return sign;
    }

    @PostMapping("/starSign")
    public String getStarSign(@RequestBody String birthdayString) {
        LocalDate birthday = birthdayService.getValidBirthday(birthdayString);
        String sign = birthdayService.getStarSign(birthday);

        return sign;
    }

    @ExceptionHandler(RuntimeException.class)
    public final ResponseEntity handleAllExceptions(RuntimeException ex) {

        return new ResponseEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Аннотации Spring MVC

Во-первых, вы заметите следующие примечания вверху. Аннотация @RestController сообщает системе, что этот файл является “контроллером Rest API”, что просто означает, что он содержит набор конечных точек API. Вы также можете использовать аннотацию @Controller , но это означает, что вам придется добавить больше шаблонного кода для преобразования ответов в HTTP OK вместо простого возврата значений. Вторая строка сообщает ему, что все конечные точки имеют префикс “/birthday” в пути. Позже я покажу полный путь к конечной точке.

@RestController
@RequestMapping("/birthday")

Впрыск конструктора с пружиной

Далее вы увидите переменную класса для службы дня рождения ((типа BirthdayService ). Эта переменная инициализируется в конструкторе класса. Начиная с Spring Framework 4.3, вам больше не нужно указывать @Autowired при использовании инъекции конструктора. Это приведет к загрузке экземпляра класса Basic Birthday Service , который мы вскоре рассмотрим.

private final BirthdayService birthdayService;

BirthdayInfoController(BirthdayService birthdayService){
    this.birthdayService = birthdayService;
}

Обработка сообщений в Вашем API

Следующие несколько методов ( getDayOfWeek , get Chinese Zodiac и get StarSign |/) - это то, где все становится пикантным. Они являются обработчиками для трех разных конечных точек. Каждый из них начинается с аннотации @PostMapping , которая сообщает системе путь к конечной точке. В этом случае путь будет /день рождения/день Недели (день рождения / префикс взят из аннотации @RequestMapping выше).

@PostMapping("/dayOfWeek")

Каждый метод конечной точки выполняет следующее:

  • Принимает строку birthday String string.
  • Использует службу birthday для проверки того, может ли строка birthday string быть преобразована в объект LocalDate . Если это так, возвращает объект Local Data для использования в более позднем коде. Если нет, выдает ошибку (см. Обработку ошибок ниже).
  • Возвращает возвращаемое значение (день недели, китайский знак зодиака или астрологический знак) из службы birthday .
  • Возвращает строку (которая заставит ее ответить HTTP OK под капотом).

Обработка ошибок в RestController

Наконец, существует метод обработки ошибок:

@ExceptionHandler(RuntimeException.class)
public final ResponseEntity handleAllExceptions(RuntimeException ex) {

    return new ResponseEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR);
}

Здесь аннотация @ExceptionHandler сообщает ему перехватывать любой экземпляр RuntimeException в функциях конечной точки и возвращать ответ 500.

BasicBirthdayService.java обрабатывает основную часть фактической бизнес-логики в этом приложении. Это класс, который имеет функцию для проверки правильности строки дня рождения, а также функции, которые вычисляют день недели, китайский Зодиак и астрологический знак по дате рождения.

package com.example.joy.myFirstSpringBoot.services;

import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@Service
public class BasicBirthdayService implements BirthdayService {
    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate getValidBirthday(String birthdayString) {
        if (birthdayString == null) {
            throw new RuntimeException("Must include birthday");
        }
        try {
            LocalDate birthdate = LocalDate.parse(birthdayString, formatter);
            return birthdate;
        } catch (Exception e) {
            throw new RuntimeException("Must include valid birthday in yyyy-MM-dd format");
        }

    }

    @Override
    public String getBirthDOW(LocalDate birthday) {
        return birthday.getDayOfWeek().toString();
    }

    @Override
    public String getChineseZodiac(LocalDate birthday) {
        int year = birthday.getYear();
        switch (year % 12) {
            case 0:
                return "Monkey";
            case 1:
                return "Rooster";
            case 2:
                return "Dog";
            case 3:
                return "Pig";
            case 4:
                return "Rat";
            case 5:
                return "Ox";
            case 6:
                return "Tiger";
            case 7:
                return "Rabbit";
            case 8:
                return "Dragon";
            case 9:
                return "Snake";
            case 10:
                return "Horse";
            case 11:
                return "Sheep";
        }

        return "";
    }

    @Override
    public String getStarSign(LocalDate birthday) {
        int day = birthday.getDayOfMonth();
        int month = birthday.getMonthValue();

        if (month == 12 && day >= 22 || month == 1 && day < 20) {
            return "Capricorn";
        } else if (month == 1 && day >= 20 || month == 2 && day < 19) {
            return "Aquarius";
        } else if (month == 2 && day >= 19 || month == 3 && day < 21) {
            return "Pisces";
        } else if (month == 3 && day >= 21 || month == 4 && day < 20) {
            return "Aries";
        } else if (month == 4 && day >= 20 || month == 5 && day < 21) {
            return "taurus";
        } else if (month == 5 && day >= 21 || month == 6 && day < 21) {
            return "Gemini";
        } else if (month == 6 && day >= 21 || month == 7 && day < 23) {
            return "Cancer";
        } else if (month == 7 && day >= 23 || month == 8 && day < 23) {
            return "Leo";
        } else if (month == 8 && day >= 23 || month == 9 && day < 23) {
            return "Virgo";
        } else if (month == 9 && day >= 23 || month == 10 && day < 23) {
            return "Libra";
        } else if (month == 10 && day >= 23 || month == 11 && day < 22) {
            return "Scorpio";
        } else if (month == 11 && day >= 22 || month == 12 && day < 22) {
            return "Sagittarius";
        }
        return "";
    }
}

Аннотация @Service – это то, что он использует для внедрения этого в конструктор Birthday InfoController . Поскольку этот класс реализует интерфейс Birthday Service и находится в пределах пути сканирования для приложения, Spring найдет его, инициализирует и введет в конструктор в BirthdayInfoController .

Остальная часть класса – это просто набор функций, которые определяют бизнес-логику, вызываемую из BirthdayInfoController .

Запустите Свой Базовый Spring HTTP REST API

На этом этапе у вас должен быть работающий API. В Eclipse просто щелкните правой кнопкой мыши на Spring Boot Rest Api Application file и выберите выполнить как > Java-приложение , и оно запустит его. Чтобы попасть в конечные точки, вы можете использовать curl для выполнения этих команд:

День недели:

Запрос:

curl -X POST \
  http://localhost:8080/birthday/dayOfWeek \
  -H 'Content-Type: text/plain' \
  -H 'accept: text/plain' \
  -d 2005-03-09

Ответ:

WEDNESDAY

Китайский Зодиак:

Запрос:

curl -X POST \
  http://localhost:8080/birthday/chineseZodiac \
  -H 'Content-Type: text/plain' \
  -H 'accept: text/plain' \
  -d 2005-03-09

Ответ:

Rooster

Астрологический Знак:

Запрос:

curl -X POST \
  http://localhost:8080/birthday/starSign \
  -H 'Content-Type: text/plain' \
  -H 'accept: text/plain' \
  -d 2005-03-09

Ответ:

Pisces

Защитите свое Java-приложение JUnit 5 с помощью OAuth 2.0

Теперь, когда у нас есть созданный базовый API, давайте сделаем его безопасным! Вы можете сделать это быстро, используя проверку токена OAuth 2.0 от Okta. Почему Okta? Okta – это поставщик удостоверений, который позволяет легко добавлять аутентификацию и авторизацию в ваши приложения. Он всегда включен, и друзья не позволяют друзьям писать аутентификацию.

После интеграции Okta API потребует от пользователя ввести токен доступа OAuth 2.0. Этот токен будет проверен Okta на действительность и подлинность.

Чтобы сделать это, вам нужно будет настроить “Сервисное приложение” с помощью Okta, добавить Okta Spring Boot starter в Java-код и иметь способ генерировать токены для этого приложения. Давайте начнем!

Создайте приложение OpenID Connect

Вам нужно будет создать приложение OpenID Connect в Okta, чтобы получить ваши уникальные значения для выполнения аутентификации.

Чтобы сделать это, вы должны сначала войти в свою учетную запись разработчика Okta (или зарегистрироваться если у вас нет учетной записи).

Оказавшись на панели инструментов разработчика Okta, перейдите на вкладку Приложения в верхней части экрана, а затем нажмите на кнопку Добавить приложение .

Вы увидите следующий экран. Нажмите на плитку Service , а затем нажмите Next .

На следующем экране вам будет предложено ввести название вашего приложения. Выберите что-то, что имеет смысл, и нажмите Готово .

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

Интегрируйте безопасную аутентификацию в Свой код

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

Создайте файл с именем src/main/resources/application.properties со следующим содержимым:

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={clientId}
okta.oauth2.clientSecret={clientSecret}
okta.oauth2.scope=openid

Замените элементы внутри {...} вашими значениями. |/{Идентификатор клиента} и {clientSecret} значения будут получены из приложения, которое вы только что создали. После того, как вы настроили контекст приложения, все, что вам нужно сделать, это добавить одну зависимость к вашему pom.xml файл и создайте еще один Java-файл.

Для зависимостей добавьте стартер загрузки Okra Spring в pom.xml файл в разделе зависимостей:



    com.okta.spring
    okta-spring-boot-starter
    1.1.0



И последний шаг – обновить приложение Spring Boot Rest Api , чтобы включить подкласс статической конфигурации, называемый OktaOAuth2WebSecurityConfigurerAdapter . Ваше приложение springbootrestapi.java файл должен быть обновлен, чтобы выглядеть следующим образом:

package com.example.joy.myFirstSpringBoot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@SpringBootApplication(scanBasePackages = { "com.example.joy" })
public class SpringBootRestApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootRestApiApplication.class, args);
    }

    @Configuration
    static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .anyRequest().authenticated().and()
            .oauth2ResourceServer().jwt();
        }
    }
}

Создайте токен для тестирования Вашего приложения Spring Boot с помощью JUnit 5

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

Простой способ получить токен – сгенерировать его с помощью OpenID Connect Debugger/| . Однако сначала у вас должен быть клиент Настройка веб-приложения в Okta для использования с неявным потоком OpenID Connect.

Для этого вернитесь в консоль разработчика Okta и выберите Приложения > Добавить приложение , но на этот раз выберите плитку Web |/.

На следующем экране вам нужно будет заполнить некоторую информацию. Установите имя на то, что вы запомните как свое веб-приложение. Установите в поле URI перенаправления входа в систему значение Установите в поле URI перенаправления входа в систему значение и Разрешенный тип гранта для/| Гибрид . Нажмите

Теперь перейдите на веб-сайт OpenID Connect debugger , заполните форму, как показано на рисунке ниже (не забудьте ввести идентификатор клиента для вашего недавно созданного приложения Okta web ). Поле state//должно быть заполнено, но может содержать любые символы. URI авторизации должен начинаться с URL-адреса вашего домена (находится на панели управления Okta).:

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

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

Протестируйте Свое защищенное приложение Spring Boot с помощью JUnit 5

Теперь у вас должен быть работающий безопасный API. Давайте посмотрим на это в действии! В Eclipse просто щелкните правой кнопкой мыши на Spring Boot Rest Api Application file, выберите выполнить как > Java-приложение , и оно запустит его. Чтобы попасть в конечные точки, вы можете использовать curl для выполнения этих команд, но обязательно включите новый заголовок, содержащий ваш токен. Заменить {токен идет сюда} с фактическим токеном из OpenID Connect:

День недели:

Запрос:

curl -X POST \
  http://localhost:8080/birthday/dayOfWeek \
  -H 'Authorization: Bearer {token goes here}' \
  -H 'Content-Type: text/plain' \
  -H 'accept: text/plain' \
  -d 2005-03-09

Ответ:

WEDNESDAY

Китайский Зодиак:

Запрос:

curl -X POST \
  http://localhost:8080/birthday/chineseZodiac \
  -H 'Authorization: Bearer {token goes here}' \
  -H 'Content-Type: text/plain' \
  -H 'accept: text/plain' \
  -d 2005-03-09

Ответ:

Rooster

Астрологический Знак:

Запрос:

curl -X POST \
  http://localhost:8080/birthday/starSign \
  -H 'Authorization: Bearer {token goes here}' \
  -H 'Content-Type: text/plain' \
  -H 'accept: text/plain' \
  -d 2005-03-09

Ответ:

Pisces

Добавьте Модульный и интеграционный тест в свое Java-приложение с помощью JUnit 5

Поздравляю! Теперь у вас есть безопасный API, который предоставляет вам удобную информацию о любой дате рождения, которую вы только можете себе представить! Что осталось? Что ж, вам следует добавить несколько модульных тестов, чтобы убедиться, что он работает хорошо.

Многие люди совершают ошибку, смешивая модульные тесты и интеграционные тесты (также называемые сквозными или E2E-тестами). Ниже я опишу разницу между этими двумя типами.

Прежде чем приступить к модульным тестам, добавьте еще одну зависимость в pom.xml файл (в разделе <зависимости> ).


    org.springframework.security
    spring-security-test
    test


Модульные тесты

По большей части модульные тесты предназначены для тестирования небольшого фрагмента (или единицы) кода. Обычно это ограничивается кодом внутри функции или иногда распространяется на некоторые вспомогательные функции, вызываемые из этой функции. Если модульный тест тестирует код, который зависит от другой службы или ресурса, например базы данных или сетевого ресурса, модульный тест должен “имитировать” и вводить эту зависимость, чтобы не оказывать фактического влияния на этот внешний ресурс. Это также ограничивает фокус только тем тестируемым устройством. Чтобы имитировать зависимость, вы можете либо использовать макет библиотеки, такой как “Mockito”, либо просто передать другую реализацию зависимости, которую вы хотите заменить. Издевательство выходит за рамки этой статьи, и я просто покажу примеры модульных тестов для BasicBirthdayService .

В BasicBirthdayServiceTest.java файл содержит модульные тесты класса Basic Birthday Service .

package com.example.joy.myFirstSpringBoot.services;

import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;

class BasicBirthdayServiceTest {
    BasicBirthdayService birthdayService = new BasicBirthdayService();

    @Test
    void testGetBirthdayDOW() {
        String dow = birthdayService.getBirthDOW(LocalDate.of(1979,7,14));
        assertEquals("SATURDAY",dow);
        dow = birthdayService.getBirthDOW(LocalDate.of(2018,1,23));
        assertEquals("TUESDAY",dow);
        dow = birthdayService.getBirthDOW(LocalDate.of(1972,3,17));
        assertEquals("FRIDAY",dow);
        dow = birthdayService.getBirthDOW(LocalDate.of(1945,12,2));
        assertEquals("SUNDAY",dow);
        dow = birthdayService.getBirthDOW(LocalDate.of(2003,8,4));
        assertEquals("MONDAY",dow);
    }

    @Test
    void testGetBirthdayChineseSign() {
        String dow = birthdayService.getChineseZodiac(LocalDate.of(1979,7,14));
        assertEquals("Sheep",dow);
        dow = birthdayService.getChineseZodiac(LocalDate.of(2018,1,23));
        assertEquals("Dog",dow);
        dow = birthdayService.getChineseZodiac(LocalDate.of(1972,3,17));
        assertEquals("Rat",dow);
        dow = birthdayService.getChineseZodiac(LocalDate.of(1945,12,2));
        assertEquals("Rooster",dow);
        dow = birthdayService.getChineseZodiac(LocalDate.of(2003,8,4));
        assertEquals("Sheep",dow);
    }

    @Test
    void testGetBirthdayStarSign() {
        String dow = birthdayService.getStarSign(LocalDate.of(1979,7,14));
        assertEquals("Cancer",dow);
        dow = birthdayService.getStarSign(LocalDate.of(2018,1,23));
        assertEquals("Aquarius",dow);
        dow = birthdayService.getStarSign(LocalDate.of(1972,3,17));
        assertEquals("Pisces",dow);
        dow = birthdayService.getStarSign(LocalDate.of(1945,12,2));
        assertEquals("Sagittarius",dow);
        dow = birthdayService.getStarSign(LocalDate.of(2003,8,4));
        assertEquals("Leo",dow);
    }
}

Этот тестовый класс является одним из самых базовых наборов модульных тестов, которые вы можете создать. Он создает экземпляр класса Basic Birthday Service а затем проверяет ответы трех конечных точек с различными вводимыми датами рождения. Это отличный пример тестируемого небольшого модуля, поскольку он тестирует только одну службу и даже не требует загрузки какой-либо конфигурации или ApplicationContext для этого теста. Поскольку он только тестирует службу, он не затрагивает безопасность или интерфейс HTTP rest.

Вы можете запустить этот тест из своей IDE или с помощью Maven:

mvn test -Dtest=BasicBirthdayServiceTest

Интеграционные тесты с JUnit 5

Интеграционные тесты предназначены для тестирования всего пути интегрированного кода (от конца до конца) для конкретного варианта использования. Например, интеграционным тестом приложения Birthday может быть тот, который выполняет HTTP POST-вызов конечной точки DayOfWeek , а затем проверяет, соответствуют ли результаты ожидаемым. Этот вызов в конечном итоге попадет как в код Birthday ControllerInfo , так и в код Basic Birthday Service . Для выполнения этих вызовов также потребуется взаимодействие с уровнем безопасности. В более сложной системе интеграционный тест может попасть в базу данных, выполнить чтение или запись из сетевого ресурса или отправить электронное письмо.

Из-за использования фактических зависимостей/ресурсов интеграционные тесты обычно следует рассматривать как потенциально разрушительные и хрупкие (поскольку резервные данные могут быть изменены). По этим причинам интеграционные тесты должны быть “обработаны с осторожностью”, изолированы от обычных модульных тестов и выполняться независимо от них. Лично мне нравится использовать отдельную систему, особенно для тестирования REST API, а не JUnit 5, поскольку она полностью отделяет их от модульных тестов.

Если вы планируете писать модульные тесты с помощью JUnit 5, они должны быть названы с уникальным суффиксом, таким как “IT”. Ниже приведен пример тех же тестов, которые вы выполняли с Basic Birthday Service , за исключением того, что они написаны как интеграционный тест. Этот пример имитирует веб-безопасность для этого конкретного теста, поскольку область применения не предназначена для тестирования OAuth 2.0, хотя интеграционный тест может использоваться для тестирования всего, включая безопасность.

В BirthdayInfoControllerIT.java файл содержит интеграционные тесты трех конечных точек API для получения информации о дне рождения.

package com.example.joy.myFirstSpringBoot.controllers;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import com.example.joy.myFirstSpringBoot.services.BasicBirthdayService;

@AutoConfigureMockMvc
@ContextConfiguration(classes = {BirthdayInfoController.class, BasicBirthdayService.class})
@WebMvcTest
class BirthdayInfoControllerIT {
    private final static String TEST_USER_ID = "user-id-123";
    String bd1 = LocalDate.of(1979,7,14).format(DateTimeFormatter.ISO_DATE);
    String bd2 = LocalDate.of(2018,1,23).format(DateTimeFormatter.ISO_DATE);
    String bd3 = LocalDate.of(1972, 3, 17).format(DateTimeFormatter.ISO_DATE);
    String bd4 = LocalDate.of(1945, 12, 2).format(DateTimeFormatter.ISO_DATE);
    String bd5 = LocalDate.of(2003, 8, 4).format(DateTimeFormatter.ISO_DATE);

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testGetBirthdayDOW() throws Exception {
        testDOW(bd1,"SATURDAY");
        testDOW(bd2,"TUESDAY");
        testDOW(bd3,"FRIDAY");
        testDOW(bd4,"SUNDAY");
        testDOW(bd5,"MONDAY");
    }

    @Test
    public void testGetBirthdayChineseSign() throws Exception {
        testZodiak(bd1,"Sheep");
        testZodiak(bd2,"Dog");
        testZodiak(bd3,"Rat");
        testZodiak(bd4,"Rooster");
        testZodiak(bd5,"Sheep");
    }

    @Test
    public void testGetBirthdaytestStarSign() throws Exception {
        testStarSign(bd1,"Cancer");
        testStarSign(bd2,"Aquarius");
        testStarSign(bd3,"Pisces");
        testStarSign(bd4,"Sagittarius");
        testStarSign(bd5,"Leo");
    }

    private void testDOW(String birthday, String dow) throws Exception {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/birthday/dayOfWeek")
                    .with(user(TEST_USER_ID))
                    .with(csrf())
                    .content(birthday)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                     .andReturn();

        String resultDOW = result.getResponse().getContentAsString();
        assertNotNull(resultDOW);
        assertEquals(dow, resultDOW);
    }

    private void testZodiak(String birthday, String czs) throws Exception {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/birthday/chineseZodiac")
                    .with(user(TEST_USER_ID))
                    .with(csrf())
                    .content(birthday)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                     .andReturn();

        String resultCZ =result.getResponse().getContentAsString();
        assertNotNull(resultCZ);
        assertEquals(czs, resultCZ);
    }

    private void testStarSign(String birthday, String ss) throws Exception {
        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/birthday/starSign")
                    .with(user(TEST_USER_ID))
                    .with(csrf())
                    .content(birthday)
                    .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                     .andReturn();

        String resultSS = result.getResponse().getContentAsString();
        assertNotNull(resultSS);
        assertEquals(ss, resultSS);
    }
}

В этом тестовом классе есть довольно много интересного; давайте рассмотрим несколько ключевых пунктов.

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

.with(user(TEST_USER_ID))
.with(csrf())

MockMvc – это просто удобная система, встроенная в Spring Framework, позволяющая нам выполнять вызовы REST API. Аннотация класса @AutoConfigureMockMvc и @Autowired для переменной-члена MockMvc сообщают системе об автоматической настройке и инициализации MockMvc объект (и в фоновом режиме контекст приложения ) для этого приложения. Он загрузит Spring Boot Rest Api-приложение и позволит тестам выполнять HTTP-вызовы к нему.

Если вы прочитаете о тестовой нарезке , вы можете оказаться в кроличьей норе и почувствовать желание вырвать себе волосы. Однако, если вы выберетесь из кроличьей норы, вы можете увидеть, что нарезка тестов – это просто обрезка того, что загружается в ваше приложение для конкретного модульного теста или класса интеграционных тестов. Например, если у вас есть 15 контроллеров в вашем веб-приложении с автоматически подключаемыми службами для каждого, но ваш тест тестирует только один из них, зачем беспокоиться о загрузке остальных 14 и их автоматически подключаемых служб? Вместо этого просто загрузите контроллер, который вы тестируете, и вспомогательные классы, необходимые для этого контроллера! Итак, давайте посмотрим, как тестовые фрагменты используются в этом интеграционном тесте!

@ContextConfiguration(classes = {BirthdayInfoController.class, BasicBirthdayService.class})
@WebMvcTest

Аннотация Web Mvc Test является ядром нарезки приложения WebMVC. Он сообщает системе, что вы выполняете нарезку, а @ContextConfiguration точно сообщает ей, какие контроллеры и зависимости следует загружать. Я включил службу Birthday InfoController , потому что это контроллер, который я тестирую. Если бы я это упустил, эти тесты провалились бы. Я также включил Basic Birthday Service , поскольку это интеграционный тест, и я хочу, чтобы он продолжался и автоматически подключал эту службу в качестве зависимости к контроллеру. Если бы это не был интеграционный тест, я мог бы издеваться над этой зависимостью вместо того, чтобы загружать ее вместе с контроллером.

И это все! Нарезка не должна быть слишком сложной!

Вы можете запустить этот тест из своей IDE или с помощью Maven:

mvn test -Dtest=BirthdayInfoControllerIT

Изолировать модульные и интеграционные тесты

В Eclipse, если вы щелкните правой кнопкой мыши на папке и выберите выполнить как > JUnit Test , он будет запускать все модульные тесты и интеграционные тесты без каких-либо предубеждений. Однако часто желательно, особенно если выполняется как часть автоматизированного процесса, либо просто запускать модульные тесты, либо запускать и то, и другое. Таким образом, можно быстро проверить работоспособность модулей без выполнения иногда разрушительных интеграционных тестов. Существует много подходов для этого, но один простой способ – добавить Maven Failsafe Plugin в ваш проект. Это делается путем обновления раздела pom.xml файл следующим образом:


    
        
            org.springframework.boot
            spring-boot-maven-plugin
        
        
            org.apache.maven.plugins
            maven-failsafe-plugin
        
    


Плагин Failsafe будет различать типы тестов по названиям. По умолчанию он будет рассматривать любой тест, который начинается или заканчивается с IT , как интеграционный тест. Он также рассматривает тесты, которые заканчиваются на IT Case интеграционный тест.

Как только pom.xml настроен, вы можете запустить test или verify цели для тестирования либо модульных тестов, либо модульных и интеграционных тестов соответственно. Из Eclipse это делается путем перехода к проекту, щелчка правой кнопкой мыши и выбора выполнить как > Maven test для цели test |/. Для проверки цели вы должны нажать на выполнить как > Сборка Maven… а затем введите “подтвердить” в текстовое поле цели и нажмите кнопку Выполнить. Из командной строки это можно сделать с помощью mvn test и mvn проверяет .

Добавьте покрытие кода в Ваше Java-приложение с помощью JUnit 5

Идея “покрытия кода” – это вопрос о том, какая часть вашего кода тестируется с помощью ваших модульных и/или интеграционных тестов. Есть много инструментов, которые разработчик может использовать для этого, но поскольку мне нравится Eclipse, я обычно использую инструмент под названием EclEmma . В более старых версиях Eclipse нам приходилось устанавливать этот плагин отдельно, но, похоже, в настоящее время он устанавливается по умолчанию при установке версий Eclipse EE. Если он не может быть найден, вы всегда можете перейти на Eclipse Marketplace (из меню справки Eclipse) и установить его самостоятельно.

Из Eclipse запустить EclEmma очень просто. Просто щелкните правой кнопкой мыши на одном тестовом классе или папке и выберите покрытие как > Тест JUnit . Это позволит выполнить ваш модульный тест или тесты, но также предоставит вам отчет о покрытии (см. нижнюю часть изображения ниже). Кроме того, он выделит любой код в вашем приложении, который выделен зеленым цветом, и все, что не выделено красным. (Он будет охватывать частичное покрытие, например, оператор if, который проверяется как true, но не как false с желтым цветом).

совет: Если вы заметили, что он оценивает охват ваших тестовых примеров и хочет, чтобы это было удалено, перейдите в Настройки > Java > Покрытие кода и установите параметр “Совпадение только записей пути” в src/main/java .

Узнайте больше о Java и Spring Boot, безопасных REST API, Android C

Я надеюсь, что вы зашли так далеко и получили удовольствие от этого пошагового руководства о том, как создать и протестировать безопасный REST API с помощью Spring Boot и JUnit 5.

Полный исходный код доступен на GitHub .

Для получения дополнительной информации о Spring Boot или OpenID Connect ознакомьтесь с этими руководствами:

Для получения дополнительной информации о JUnit 5 и тестовых фрагментах ознакомьтесь с этими источниками:

Если вы зашли так далеко, вам может быть интересно посмотреть будущие записи в блоге. Подписывайтесь на мою команду @oktadev в Twitter или посетите наш канал на YouTube . Если у вас есть вопросы, пожалуйста, оставьте комментарий ниже.

Оригинал: “https://dev.to/oktadev/test-your-spring-boot-applications-with-junit-5-3g3l”