Тестване в Spring Boot

1. Общ преглед

В този урок ще разгледаме тестовете за писане с помощта на рамковата поддръжка в Spring Boot. Ще разгледаме модулни тестове, които могат да се изпълняват изолирано, както и интеграционни тестове, които ще стартират Spring Spring контекста преди изпълнението на тестовете.

Ако сте нов в Spring Boot, разгледайте нашето въведение в Spring Boot.

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

Приложението, което ще използваме в тази статия, е API, който предоставя някои основни операции на ресурс на служител . Това е типична многостепенна архитектура - API извикването се обработва от контролера към услугата до слоя за устойчивост .

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

Нека първо добавим нашите тестови зависимости:

 org.springframework.boot spring-boot-starter-test test 2.2.6.RELEASE   com.h2database h2 test 

В пролетно-обувка стартер Тестът е основният зависимостта, която съдържа по-голямата част от елементите, необходими за нашите тестове.

H2 DB е нашата база данни в паметта. Той елиминира необходимостта от конфигуриране и стартиране на действителна база данни за целите на теста.

4. Тестване на интеграция с @DataJpaTest

Ще работим с обект на име Employee, който има id и име като свои свойства:

@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, база данни в паметта
  • настройка на хибернация, Spring Data и DataSource
  • извършване на @EntityScan
  • включване на SQL регистриране

За да извършим DB операции, са ни необходими някои записи, които вече са в нашата база данни. За да настроим тези данни, можем да използваме TestEntityManager.

Spring Boot TestEntityManager е алтернатива на стандартния JPA EntityManager, който предоставя методи, често използвани при писане на тестове.

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, за да вмъкнем служител в DB и да го прочетем чрез API за намиране по име.

Частта assertThat (...) идва от библиотеката Assertj, която се доставя в комплект с Spring Boot.

5. Подигравка с @MockBean

Кодът на нашия сервизен слой зависи от нашето хранилище .

Въпреки това, за да тестваме нивото на услугата , не е нужно да знаем или да се интересуваме от това как се изпълнява слоят за устойчивост:

@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Override public Employee getEmployeeByName(String name) { return employeeRepository.findByName(name); } }

В идеалния случай би трябвало да можем да напишем и тестваме кода на нивото на сервизния ни слой, без да свързваме в пълния ни слой на постоянство.

За да постигнем това, можем да използваме подигравателната поддръжка, предоставена от 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 предоставя анотацията @TestConfiguration, която можем да добавим към класове в src / test / java, за да покажем , че те не трябва да се взимат чрез сканиране.

Друго интересно нещо тук е използването на @MockBean . Той създава 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

Нашият контролер зависи от нивото на услугата ; нека включим само един метод за простота:

@RestController @RequestMapping("/api") public class EmployeeRestController { @Autowired private EmployeeService employeeService; @GetMapping("/employees") public List getAllEmployees() { return employeeService.getAllEmployees(); } }

Тъй като се фокусираме само върху кода на контролера , естествено е да се подиграваме на кода на сервизния слой за нашите модулни тестове:

@RunWith(SpringRunner.class) @WebMvcTest(EmployeeRestController.class) public class EmployeeRestControllerIntegrationTest { @Autowired private MockMvc mvc; @MockBean private EmployeeService service; // write test cases here }

За да тестваме контролерите , можем да използваме @WebMvcTest . Той ще конфигурира автоматично пролетната 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 глаголи като пут () , след () и т.н. Моля, имайте предвид, че ние сме също така за определяне на типа съдържание в искането.

MockMvc е гъвкав и ние можем да създадем всяка заявка, използвайки го.

7. Тестване на интеграция с @SpringBootTest

Както подсказва името, тестовете за интеграция се фокусират върху интегрирането на различни слоеве на приложението. Това също означава, че не става въпрос за подигравки.

В идеалния случай трябва да държим тестовете за интеграция отделени от модулните тестове и да не изпълняваме заедно с модулните тестове. Можем да направим това, като използваме различен профил, за да стартираме само тестовете за интеграция. Няколко причини за това могат да бъдат, че тестовете за интеграция отнемат много време и може да се наложи действителна база данни за изпълнение.

В тази статия обаче няма да се фокусираме върху това и вместо това ще използваме хранилището за постоянство 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 .

The application-integrationtest.properties contains the details to configure the persistence storage:

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

If we want to run our integration tests against MySQL, we can change the above values in the properties file.

The test cases for the integration tests might look similar to the Controller layer unit tests:

@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"))); }

The difference from the Controller layer unit tests is that here nothing is mocked and end-to-end scenarios will be executed.

8. Auto-Configured Tests

One of the amazing features of Spring Boot's auto-configured annotations is that it helps to load parts of the complete application and test-specific layers of the codebase.

In addition to the above-mentioned annotations, here's a list of a few widely used annotations:

  • @WebFluxTest: We can use the @WebFluxTest annotation to test Spring WebFlux controllers. It's often used along with @MockBean to provide mock implementations for required dependencies.
  • @JdbcTest: We can use the @JdbcTest annotation to test JPA applications, but it's for tests that only require a DataSource. The annotation configures an in-memory embedded database and a JdbcTemplate.
  • @JooqTest: To test jOOQ-related tests, we can use @JooqTest annotation, which configures a DSLContext.
  • @DataMongoTest: To test MongoDB applications, @DataMongoTest is a useful annotation. By default, it configures an in-memory embedded MongoDB if the driver is available through dependencies, configures a MongoTemplate, scans for @Document classes, and configures Spring Data MongoDB repositories.
  • @DataRedisTestmakes it easier to test Redis applications. It scans for @RedisHash classes and configures Spring Data Redis repositories by default.
  • @DataLdapTest configures an in-memory embedded LDAP (if available), configures a LdapTemplate, scans for @Entry classes, and configures Spring Data LDAP repositories by default.
  • @RestClientTest: We generally use the @RestClientTest annotation to test REST clients. It auto-configures different dependencies such as Jackson, GSON, and Jsonb support; configures a RestTemplateBuilder; and adds support for MockRestServiceServer by default.

9. Conclusion

In this article, we took a deep dive into the testing support in Spring Boot and showed how to write unit tests efficiently.

Пълният изходен код на тази статия може да бъде намерен в GitHub. Изходният код съдържа много повече примери и различни тестови случаи.

И ако искате да продължите да научавате за тестване, имаме отделни статии, свързани с интеграционни тестове и модулни тестове в JUnit 5.