Thử nghiệm trong Spring Boot

1. Khái quát chung

Trong hướng dẫn này, chúng ta sẽ xem xét các bài kiểm tra viết bằng cách sử dụng hỗ trợ khung trong Spring Boot. Chúng tôi sẽ đề cập đến các bài kiểm tra đơn vị có thể chạy riêng lẻ cũng như các bài kiểm tra tích hợp sẽ khởi động bối cảnh Spring trước khi thực hiện các bài kiểm tra.

Nếu bạn chưa quen với Spring Boot, hãy xem phần giới thiệu của chúng tôi về Spring Boot.

2. Thiết lập dự án

Ứng dụng chúng ta sẽ sử dụng trong bài viết này là một API cung cấp một số thao tác cơ bản trên Tài nguyên nhân viên . Đây là một kiến ​​trúc tầng điển hình - lệnh gọi API được xử lý từ Bộ điều khiển đến Dịch vụ đến tầng Kiên trì .

3. Sự phụ thuộc của Maven

Trước tiên, hãy thêm các phụ thuộc thử nghiệm của chúng tôi:

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

Các lò xo khởi động khởi động kiểm tra là phụ thuộc chủ yếu có chứa hầu hết các yếu tố cần thiết cho các bài kiểm tra của chúng tôi.

H2 DB là cơ sở dữ liệu trong bộ nhớ của chúng tôi. Nó giúp loại bỏ nhu cầu cấu hình và khởi động một cơ sở dữ liệu thực tế cho các mục đích kiểm tra.

4. Kiểm tra tích hợp với @DataJpaTest

Chúng tôi sẽ làm việc với một thực thể có tên là Employee,idtên là thuộc tính của nó:

@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 }

Và đây là kho lưu trữ của chúng tôi sử dụng Spring Data JPA:

@Repository public interface EmployeeRepository extends JpaRepository { public Employee findByName(String name); }

Đó là nó cho mã lớp bền bỉ. Bây giờ chúng ta hãy hướng tới việc viết lớp kiểm tra của chúng ta.

Đầu tiên, hãy tạo khung của lớp thử nghiệm của chúng ta:

@RunWith(SpringRunner.class) @DataJpaTest public class EmployeeRepositoryIntegrationTest { @Autowired private TestEntityManager entityManager; @Autowired private EmployeeRepository employeeRepository; // write test cases here }

@RunWith (SpringRunner.class) cung cấp cầu nối giữa các tính năng kiểm tra Spring Boot và JUnit. Bất cứ khi nào chúng tôi sử dụng bất kỳ tính năng kiểm tra Spring Boot nào trong các bài kiểm tra JUnit của chúng tôi, chú thích này sẽ được yêu cầu.

@DataJpaTest cung cấp một số thiết lập tiêu chuẩn cần thiết để kiểm tra lớp bền vững:

  • cấu hình H2, cơ sở dữ liệu trong bộ nhớ
  • cài đặt Hibernate, Spring Data và DataSource
  • thực hiện @EntityScan
  • bật ghi nhật ký SQL

Để thực hiện các hoạt động DB, chúng ta cần một số bản ghi đã có trong cơ sở dữ liệu của mình. Để thiết lập dữ liệu này, chúng ta có thể sử dụng TestEntityManager.

Spring Boot TestEntityManager là một giải pháp thay thế cho JPA EntityManager tiêu chuẩn , cung cấp các phương pháp thường được sử dụng khi viết các bài kiểm tra.

EmployeeRepository là thành phần mà chúng tôi sẽ kiểm tra.

Bây giờ chúng ta hãy viết trường hợp thử nghiệm đầu tiên của chúng ta:

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

Trong thử nghiệm trên, chúng tôi đang sử dụng TestEntityManager để chèn một Nhân viên vào DB và đọc nó thông qua API tìm theo tên.

Phần khẳng định (…) đến từ thư viện Assertj, đi kèm với Spring Boot.

5. Chế giễu với @MockBean

Mã lớp Dịch vụ của chúng tôi phụ thuộc vào Kho lưu trữ của chúng tôi .

Tuy nhiên, để kiểm tra lớp Dịch vụ , chúng ta không cần biết hoặc quan tâm đến cách lớp bền vững được triển khai:

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

Lý tưởng nhất là chúng ta có thể viết và kiểm tra mã lớp Dịch vụ của mình mà không cần nối dây trong lớp liên tục đầy đủ của chúng ta.

Để đạt được điều này, chúng ta có thể sử dụng hỗ trợ giả lập do Spring Boot Test cung cấp.

Trước tiên, hãy xem qua bộ xương của lớp thử nghiệm:

@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 }

Để kiểm tra lớp Dịch vụ , chúng ta cần có một thể hiện của lớp Dịch vụ được tạo và có sẵn dưới dạng @Bean để chúng ta có thể @Autowire nó trong lớp thử nghiệm của mình. Chúng tôi có thể đạt được cấu hình này bằng cách sử dụng chú thích @TestConfiguration .

Trong quá trình quét thành phần, chúng tôi có thể thấy rằng các thành phần hoặc cấu hình chỉ được tạo cho các thử nghiệm cụ thể vô tình bị nhặt ở khắp mọi nơi. Để giúp ngăn chặn điều này, Spring Boot cung cấp chú thích @TestConfiguration mà chúng ta có thể thêm vào các lớp trong src / test / java để chỉ ra rằng chúng không nên được chọn bằng cách quét.

Một điều thú vị khác ở đây là việc sử dụng @MockBean . Nó tạo Mock cho EmployeeRepository , có thể được sử dụng để bỏ qua lệnh gọi đến EmployeeRepository thực :

@Before public void setUp() { Employee alex = new Employee("alex"); Mockito.when(employeeRepository.findByName(alex.getName())) .thenReturn(alex); }

Vì quá trình thiết lập được thực hiện, trường hợp thử nghiệm sẽ đơn giản hơn:

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

6. Kiểm tra đơn vị với @WebMvcTest

Bộ điều khiển của chúng tôi phụ thuộc vào lớp Dịch vụ ; chúng ta hãy chỉ bao gồm một phương pháp duy nhất để đơn giản:

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

Vì chúng tôi chỉ tập trung vào mã Bộ điều khiển , nên việc giả mạo mã lớp Dịch vụ cho các bài kiểm tra đơn vị của chúng tôi là điều đương nhiên:

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

Để kiểm tra Bộ điều khiển , chúng tôi có thể sử dụng @WebMvcTest . Nó sẽ tự động cấu hình cơ sở hạ tầng Spring MVC cho các bài kiểm tra đơn vị của chúng tôi.

Trong hầu hết các trường hợp, @ WebMvcTest sẽ bị giới hạn trong việc khởi động một bộ điều khiển duy nhất. Chúng tôi cũng có thể sử dụng nó cùng với @MockBean để cung cấp các triển khai giả cho bất kỳ phụ thuộc bắt buộc nào.

@WebMvcTest cũng tự động định cấu hình MockMvc , cung cấp một cách mạnh mẽ để dễ dàng kiểm tra bộ điều khiển MVC mà không cần khởi động máy chủ HTTP đầy đủ.

Đã nói rằng, hãy viết trường hợp thử nghiệm của chúng tôi:

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

Lời gọi phương thức get (…) có thể được thay thế bằng các phương thức khác tương ứng với các động từ HTTP như put () , post () , v.v. Xin lưu ý rằng chúng tôi cũng đang đặt loại nội dung trong yêu cầu.

MockMvc rất linh hoạt và chúng tôi có thể tạo bất kỳ yêu cầu nào bằng cách sử dụng nó.

7. Kiểm tra tích hợp với @SpringBootTest

Như tên cho thấy, các bài kiểm tra tích hợp tập trung vào việc tích hợp các lớp khác nhau của ứng dụng. Điều đó cũng có nghĩa là không có sự chế giễu nào được tham gia.

Tốt nhất, chúng ta nên tách các bài kiểm tra tích hợp ra khỏi các bài kiểm tra đơn vị và không nên chạy cùng với các bài kiểm tra đơn vị. Chúng tôi có thể làm điều này bằng cách sử dụng một cấu hình khác để chỉ chạy các bài kiểm tra tích hợp. Một số lý do để làm điều này có thể là các bài kiểm tra tích hợp tốn nhiều thời gian và có thể cần một cơ sở dữ liệu thực tế để thực thi.

Tuy nhiên, trong bài viết này, chúng tôi sẽ không tập trung vào vấn đề đó và thay vào đó chúng tôi sẽ sử dụng khả năng lưu trữ liên tục H2 trong bộ nhớ.

Các bài kiểm tra tích hợp cần khởi động một vùng chứa để thực thi các trường hợp kiểm thử. Do đó, cần một số thiết lập bổ sung cho việc này - tất cả điều này đều dễ dàng trong 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 }

Các @SpringBootTest chú thích rất hữu ích khi chúng ta cần phải bootstrap toàn bộ container. Chú thích hoạt động bằng cách tạo ApplicationContext sẽ được sử dụng trong các thử nghiệm của chúng tôi.

Chúng tôi có thể sử dụng webEnvironment thuộc tính của @SpringBootTest để cấu hình môi trường thời gian chạy của chúng tôi; chúng tôi đang sử dụng WebEnosystem.MOCK ở đây để vùng chứa sẽ hoạt động trong một môi trường servlet giả.

Tiếp theo, chú thích @TestPropertySource giúp định cấu hình vị trí của các tệp thuộc tính cụ thể cho các thử nghiệm của chúng tôi. Lưu ý rằng tệp thuộc tính được tải bằng @TestPropertySource sẽ ghi đè tệp application.properties hiện có .

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.

Mã nguồn hoàn chỉnh của bài viết này có thể được tìm thấy trên GitHub. Mã nguồn chứa nhiều ví dụ hơn và nhiều trường hợp thử nghiệm khác nhau.

Và nếu bạn muốn tiếp tục tìm hiểu về kiểm thử, chúng tôi có các bài viết riêng liên quan đến kiểm thử tích hợp và kiểm thử đơn vị trong JUnit 5.