Kiểm thử trong Spring Boot
Bài viết này được dịch bài : Trần Anh Tuấn - Học viện lớp java07
Email liên hệ : tanhtuan093@gmail.com
Bài viết gốc : https://www.baeldung.com/spring-boot-testing
1. Khái quát
Trong hướng dẫn này , chúng ta sẽ xem xét việc viết viết các bài test bằng các framework được hỗ trợ trong Spring Boot . Chúng ta sẽ đề cập đến các bài test có thể chạy đơn lẻ cũng như các bài test tích hợp sẽ khởi động bootstrap Spring trước khi thực hiện các bài kiểm tra .
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ố hoạt động cơ bản trên Employee Resource . Đây là một kiến trúc phân cấp điển hình - lệnh gọi API được xử lý từ Controller đến Service đến lớp Persistence.
3 . Maven Dependencies
Đầu tiên chúng ta sẽ thêm testing dependencies :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
spring-boot-starter-test là một dependency cơ bản chứa phần lớn các yếu tố cần thiết cho các kiểm thử của chúng ta .
H2 DB là cơ sở dữ liệu dạng in-memory . Nó 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 thử.
3.1. JUnit 4
Bắt đầu với Spring Boot 2.4 , JUnit 5 đã bị xóa khỏi spring-boot-starter-test . Nếu chúng ta muốn viết test sử dụng JUnit4 thì chúng ta sẽ phải thêm thứ sau vào Maven dependency :
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
4 . 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ó đối tượng giả nào được tham gia . Chúng ta 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 .
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 ta 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 vài 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 ta sẽ không tập trung vào điều đó và thay vào đó, chúng ta sẽ sử dụng lưu trữ trong H2 DB .
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 tra . Do đó, cần phải có 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
}
Annotation @SpringBootTest rất hữu ích khi chúng ta cần khởi động toàn bộ vùng chứa. Annotation này 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 ta .
Chúng ta có thể sử dụng thuộc tính webEnosystem của @SpringBootTest để định cấu hình môi trường thời gian chạy của chúng ta; chúng ta đ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, annotation @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 ta. 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ó .
Application-integrationtest.properties chứa các chi tiết để định cấu hình lưu trữ lâu dài:
spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect
Nếu chúng ta muốn chạy các bài kiểm tra tích hợp của mình với MySQL , chúng ta có thể thay đổi các giá trị trên trong file properties.
Các trường hợp kiểm tra cho các bài kiểm tra tích hợp có thể trông tương tự như các bài kiểm tra đơn vị trong 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")));
}
Sự khác biệt ở các bài kiểm tra đơn vị của Controlletr ở đây là không có gì được mocked và kịch bản end to end được thực thi.
5. Cấu hình kiểm thử với @TestConfiguration
Như chúng ta đã thấy trong phần trước, một bài test được chú thích bằng @SpringBootTest sẽ khởi động toàn bộ ngữ cảnh ứng dụng, có nghĩa là chúng ta có thể @Autowire bất kỳ bean nào được chọn bằng cách quét thành phần vào bài test của chúng ta:
@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {
@Autowired
private EmployeeService employeeService;
// class code ...
}
Tuy nhiên, chúng ta có thể muốn tránh khởi động bối cảnh ứng dụng thực mà sử dụng cấu hình thử nghiệm đặc biệt. Chúng ta có thể đạt được điều này với @TestConfiguration. Có hai cách sử dụng chú thích. Trên một lớp bên trong tĩnh trong cùng một lớp thử nghiệm mà chúng ta muốn @Autowire bean:
@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {
@TestConfiguration
static class EmployeeServiceImplTestContextConfiguration {
@Bean
public EmployeeService employeeService() {
return new EmployeeService() {
// implement methods
};
}
}
@Autowired
private EmployeeService employeeService;
}
Ngoài ra, chúng ta có thể tạo một lớp cấu hình thử nghiệm riêng biệt:
@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
@Bean
public EmployeeService employeeService() {
return new EmployeeService() {
// implement methods
};
}
}
Các lớp cấu hình được chú thích bằng @TestConfiguration bị loại trừ khỏi quá trình quét thành phần, do đó chúng ta cần nhập nó một cách rõ ràng trong mọi thử nghiệm mà chúng ta muốn @Autowire nó. Chúng tôi có thể làm điều đó với chú thích @Import
@RunWith(SpringRunner.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {
@Autowired
private EmployeeService employeeService;
// remaining class code
}
6. Mocking với @MockBean
Class Service của chúng ta phụ thuộc vào Repository:
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public Employee getEmployeeByName(String name) {
return employeeRepository.findByName(name);
}
}
Tuy nhiên, để kiểm tra Service, chúng ta không cần biết hoặc quan tâm đến cách thức thực hiện lớp bền vững. Lý tưởng nhất là chúng ta có thể viết và kiểm tra Service 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, chúng ta 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 class Service, chúng ta cần có một thể hiện của class Service đượ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 ta có thể đạt được cấu hình này bằng cách sử dụng @TestConfiguration
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 kiểm tra sẽ đơn giản hơn:
@Test
public void whenValidName_thenEmployeeShouldBeFound() {
String name = "alex";
Employee found = employeeService.getEmployeeByName(name);
assertThat(found.getName())mm
.isEqualTo(name);
}
7. 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, có id và name 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 ta sử dụng Spring Data JPA:
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
public Employee findByName(String name);
}
Bây giờ chúng ta hãy hướng tới việc viết class 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 ta 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 ta, 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 SQL logging
Để 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 ta đang sử dụng TestEntityManager để chèn một Employee vào DB và đọc nó thông qua API tìm theo tên.
assertThat(…) một phần đến từ Assertj library, đi kèm với Spring Boot.
8. Kiểm tra đơn vị với @WebMvcTest
Controller của chúng ta phụ thuộc vào class Service; 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<Employee> getAllEmployees() {
return employeeService.getAllEmployees();
}
}
Vì chúng ta chỉ tập trung vào mã Controller, nên việc giả mạo Service 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 Controller, 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 ta.
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 controller duy nhất. Chúng ta 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 MVC controllers 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 ta:
@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
throws Exception {
Employee alex = new Employee("alex");
List<Employee> 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 ta cũng đang đặt loại nội dung trong yêu cầu.
MockMvc rất linh hoạt và chúng ta có thể tạo bất kỳ yêu cầu nào bằng cách sử dụng nó.
9. Auto-Configured Tests
Một trong những tính năng tuyệt vời của các chú thích được cấu hình tự động của Spring Boot là nó giúp tải các phần của ứng dụng hoàn chỉnh và các lớp kiểm tra cụ thể của codebase.
Ngoài các chú thích được đề cập ở trên, đây là danh sách một số chú thích được sử dụng rộng rãi:
- @WebFluxTest: Chúng ta có thể sử dụng chú thích @WebFluxTest để kiểm tra bộ điều khiển Spring WebFlux. Nó thường được sử dụng cùng với @MockBean để cung cấp các triển khai mô phỏng cho các phần phụ thuộc bắt buộc.
- @JdbcTest: Chúng ta có thể sử dụng chú thích*@JdbcTest* để kiểm tra các ứng dụng JPA, nhưng nó dành cho các bài kiểm tra chỉ yêu cầu DataSource. Chú thích định cấu hình cơ sở dữ liệu nhúng trong bộ nhớ và JdbcTemplate.
- @JooqTest: Để kiểm tra các bài kiểm tra liên quan đến jOOQ, chúng tôi có thể sử dụng chú thích*@JooqTest*
, chú thích này định cấu hình một DSLContext. - @DataMongoTest: Để kiểm tra các ứng dụng MongoDB, @DataMongoTest là một chú thích hữu ích. Theo mặc định, nó cấu hình MongoDB nhúng trong bộ nhớ nếu trình điều khiển có sẵn thông qua các phụ thuộc, cấu hình MongoTemplate, quét các lớp @Document và cấu hình Spring Data MongoDB repositories.
- @DataRedisTest Giúp kiểm tra các ứng dụng Redis dễ dàng hơn. Nó quét các class @RedisHash và định cấu hình kho lưu trữ Spring Data Redis theo mặc định.
- @DataLdapTest định cấu hình LDAP nhúng trong bộ nhớ (nếu có), định cấu hìnhLdapTemplate
, quét các @Entry và định cấu hình kho lưu trữ Spring Data LDAP theo mặc định. - @RestClientTest: Chúng ta thường sử dụng chú thích @RestClientTest để kiểm tra các ứng dụng khách REST. Nó tự động cấu hình các phụ thuộc khác nhau như hỗ trợ Jackson, GSON và Jsonb; cấu hình RestTemplateBuilder; và thêm hỗ trợ cho MockRestServiceServer theo mặc định.
- @JsonTest: Chỉ khởi tạo ngữ cảnh ứng dụng Spring với những bean cần thiết để kiểm tra tuần tự hóa JSON.
10. Kết luận
Trong bài viết này, chúng tôi đã đi sâu vào hỗ trợ kiểm tra trong Spring Boot và chỉ ra cách viết các bài kiểm tra đơn vị một cách hiệu quả.
Bình luận