Тестирование в Spring Boot

Тестирование в Spring Boot

1. обзор

В этой статье мы рассмотримwriting tests using the framework support in Spring Boot. Мы рассмотрим модульные тесты, которые могут выполняться изолированно, а также интеграционные тесты, которые загружают контекст Spring перед выполнением тестов.

Если вы новичок в Spring Boot, ознакомьтесь с нашимintro to Spring Boot.

Дальнейшее чтение:

Изучение Spring Boot TestRestTemplate

Узнайте, как использовать новый TestRestTemplate в Spring Boot для тестирования простого API.

Read more

Краткое руководство по @RestClientTest в Spring Boot

Краткое и практическое руководство по аннотации @RestClientTest в Spring Boot

Read more

Инъекция Mockito Mocks в бобы

В этой статье будет показано, как использовать внедрение зависимостей для вставки макетов Mockito в Spring Beans для модульного тестирования.

Read more

2. Настройка проекта

Приложение, которое мы собираемся использовать в этой статье, представляет собой API, который обеспечивает некоторые базовые операции с ресурсомEmployee. Это типичная многоуровневая архитектура - вызов API обрабатывается отController доService до уровняPersistence.

3. Maven Зависимости

Давайте сначала добавим наши тестовые зависимости:


    org.springframework.boot
    spring-boot-starter-test
    test
    2.1.6.RELEASE


    com.h2database
    h2
    test
    1.4.194

spring-boot-starter-test - это основная зависимость, которая содержит большинство элементов, необходимых для наших тестов.

H2 DB - это наша база данных в памяти. Это устраняет необходимость в настройке и запуске реальной базы данных для целей тестирования.

4. Интеграционное тестирование с@DataJpaTest

Мы собираемся работать с сущностью с именемEmployee, которая имеет в качестве свойствid иname:

@Entity
@Table(name = "person")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Size(min = 3, max = 20)
    private String name;

    // standard getters and setters, constructors
}

А вот наш репозиторий - с использованием Spring Data JPA:

@Repository
public interface EmployeeRepository extends JpaRepository {

    public Employee findByName(String name);

}

Вот и все, что касается кода уровня сохраняемости. Теперь приступим к написанию нашего тестового класса.

Сначала создадим скелет нашего тестового класса:

@RunWith(SpringRunner.class)
@DataJpaTest
public class EmployeeRepositoryIntegrationTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private EmployeeRepository employeeRepository;

    // write test cases here

}

@RunWith(SpringRunner.class) используется для обеспечения моста между функциями тестирования Spring Boot и JUnit. Всякий раз, когда мы используем какие-либо функции тестирования Spring Boot в наших тестах JUnit, эта аннотация потребуется.

@DataJpaTest предоставляет некоторую стандартную настройку, необходимую для тестирования уровня сохраняемости:

  • настройка H2, база данных в памяти

  • установка Hibernate, Spring Data иDataSource

  • выполнение@EntityScan

  • включение логирования SQL

Для выполнения какой-либо операции с БД нам нужны некоторые записи, уже настроенные в нашей базе данных. Чтобы настроить эти данные, мы можем использоватьTestEntityManager.The TestEntityManager provided by Spring Boot is an alternative to the standard JPA EntityManager that provides methods commonly used when writing tests.

EmployeeRepository - это компонент, который мы собираемся протестировать. Теперь напишем наш первый тестовый пример:

@Test
public void whenFindByName_thenReturnEmployee() {
    // given
    Employee alex = new Employee("alex");
    entityManager.persist(alex);
    entityManager.flush();

    // when
    Employee found = employeeRepository.findByName(alex.getName());

    // then
    assertThat(found.getName())
      .isEqualTo(alex.getName());
}

В приведенном выше тесте мы используемTestEntityManager для вставкиEmployee в базу данных и считываем его через API поиска по имени.

ЧастьassertThat(…) происходит отAssertj library, который поставляется в комплекте с Spring Boot.

5. Мокинг с@MockBean

Наш код уровняService зависит от нашегоRepository. Однако для тестирования уровняService нам не нужно знать или заботиться о том, как реализован уровень сохраняемости:

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

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

Для этогоwe can use the mocking support provided by Spring Boot Test.

Давайте сначала посмотрим на скелет тестового класса:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {

        @Bean
        public EmployeeService employeeService() {
            return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // write test cases here
}

Чтобы проверить классService, нам нужен экземпляр классаService, созданный и доступный как@Bean, чтобы мы могли@Autowire в нашем тестовом классе. Эта конфигурация достигается с помощью аннотации@TestConfiguration.

Во время сканирования компонентов мы можем обнаружить, что компоненты или конфигурации, созданные только для конкретных тестов, могут быть случайно обнаружены повсюду. Чтобы предотвратить это,Spring Boot provides @TestConfiguration annotation that can be used on classes in src/test/java to indicate that they should not be picked up by scanning.

Еще одна интересная вещь - использование@MockBean. Этоcreates a Mock дляEmployeeRepository, которое можно использовать для обхода вызова к фактическомуEmployeeRepository:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

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

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);

     assertThat(found.getName())
      .isEqualTo(name);
 }

6. Модульное тестирование с@WebMvcTest

НашController зависит от слояService; давайте для простоты включим только один метод:

@RestController
@RequestMapping("/api")
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List getAllEmployees() {
        return employeeService.getAllEmployees();
    }
}

Поскольку мы сосредоточены только на кодеController, естественно имитировать код уровняService для наших модульных тестов:

@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    // write test cases here
}

Чтобы проверитьControllers, мы можем использовать@WebMvcTest. Он автоматически настроит инфраструктуру Spring MVC для наших модульных тестов.

В большинстве случаев @WebMvcTest будет ограничен загрузкой одного контроллера. Он используется вместе с@MockBean для предоставления фиктивных реализаций требуемых зависимостей.

@WebMvcTest также автоматически настраиваетMockMvc, который предлагает мощный способ простого тестирования контроллеров MVC без запуска полного HTTP-сервера.

Сказав это, давайте напишем наш тестовый пример:

@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
  throws Exception {

    Employee alex = new Employee("alex");

    List allEmployees = Arrays.asList(alex);

    given(service.getAllEmployees()).willReturn(allEmployees);

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$[0].name", is(alex.getName())));
}

Вызов методаget(…) можно заменить другими методами, соответствующими HTTP-командам, напримерput(),post() и т. Д. Обратите внимание, что мы также устанавливаем тип контента в запросе.

MockMvc гибкий, и с его помощью мы можем создать любой запрос.

7. Интеграционное тестирование с@SpringBootTest

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

Ideally, we should keep the integration tests separated from the unit tests and should not run along with the unit tests. Мы можем сделать это, используя другой профиль только для выполнения интеграционных тестов. Пара причин для этого может заключаться в том, что интеграционные тесты отнимают много времени и могут нуждаться в реальной базе данных для выполнения.

Однако в этой статье мы не будем заострять внимание на этом, а вместо этого будем использовать постоянное хранилище H2 в памяти.

Интеграционные тесты должны запускать контейнер для выполнения тестовых случаев. Следовательно, для этого требуются дополнительные настройки - все это легко сделать в Spring Boot:

@RunWith(SpringRunner.class)
@SpringBootTest(
  SpringBootTest.WebEnvironment.MOCK,
  classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
  locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // write test cases here
}

Аннотацию@SpringBootTest можно использовать, когда нам нужно загрузить весь контейнер. Аннотация работает путем созданияApplicationContext, которые будут использоваться в наших тестах.

Мы можем использовать атрибутwebEnvironment для@SpringBootTest, чтобы настроить нашу среду выполнения; здесь мы используемWebEnvironment.MOCK - чтобы контейнер работал в имитационной среде сервлета.

Мы можем использовать аннотацию@TestPropertySource для настройки местоположения файлов свойств, специфичных для наших тестов. Обратите внимание, что файл свойств, загруженный с помощью@TestPropertySource, заменит существующий файлapplication.properties.

application-integrationtest.properties содержит сведения для настройки постоянного хранилища:

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

Если мы хотим запустить наши интеграционные тесты с MySQL, мы можем изменить вышеуказанные значения в файле свойств.

Контрольные примеры для интеграционных тестов могут выглядеть аналогично модульным тестам уровняController:

@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
  throws Exception {

    createTestEmployee("bob");

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content()
      .contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$[0].name", is("bob")));
}

Отличие от модульных тестов уровняController в том, что здесь ничего не имитируется, и будут выполняться сквозные сценарии.

8. Автоматически настроенные тесты

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

В дополнение к вышеупомянутым аннотациям вот список нескольких широко используемых аннотаций:

  • @WebFluxTest– we может использовать аннотацию@WebFluxTest для тестирования контроллеров Spring Webflux. Он часто используется вместе с@MockBean, чтобы предоставить фиктивные реализации для требуемых зависимостей.

  • @JdbcTest – we может использовать аннотацию@JdbcTest для тестирования приложений JPA, но она предназначена для тестов, требующих толькоDataSource.. Аннотация настраивает встроенную базу данных в памяти иJdbcTemplate.

  • @JooqTest – Для тестирования тестов, связанных с jOOQ, мы можем использовать аннотацию@JooqTest, которая настраивает DSLContext.

  • @DataMongoTest – Для тестирования приложений MongoDB@DataMongoTest - полезная аннотация. По умолчанию он настраивает встроенный MongoDB в памяти, если драйвер доступен через зависимости, настраивает сканированиеMongoTemplate, для классов@Document и настраивает репозитории Spring Data MongoDB.

  • @DataRedisTest – makes it easier to testRedis приложений. Он сканирует классы@RedisHash и по умолчанию настраивает репозитории Spring Data Redis.

  • @DataLdapTest – настраивает встроенный в памятьLDAP (если доступен), настраиваетLdapTemplate, сканирует классы@Entry и по умолчанию настраивает репозитории Spring DataLDAP

  • @RestClientTest – we обычно используют аннотацию@RestClientTest для тестирования клиентов REST. Он автоматически настраивает различные зависимости, такие как поддержка Jackson, GSON и Jsonb, настраиваетRestTemplateBuilder и по умолчанию добавляет поддержку дляMockRestServiceServer.

9. Заключение

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

Полный исходный код этой статьи может бытьfound over on GitHub. Исходный код содержит еще много примеров и различных тестовых случаев.

И, если вы хотите продолжить изучение тестирования - у нас есть отдельные статьи, связанные сintegration tests иunit tests in JUnit 5.