13.1.3 Kiểm thử Repository của R2DBC
Spring Data R2DBC bao gồm hỗ trợ viết các bài kiểm thử tích hợp cho các repository R2DBC. Cụ thể, chú thích @DataR2dbcTest, khi được đặt lên một lớp kiểm thử, sẽ khiến Spring tạo ra một ngữ cảnh ứng dụng với các repository Spring Data R2DBC đã được sinh ra như là các bean có thể được inject vào lớp kiểm thử. Cùng với StepVerifier, mà chúng ta đã sử dụng trong các chương trước, điều này cho phép chúng ta viết các bài kiểm thử tự động cho tất cả các repository mà chúng ta đã tạo.
Để đơn giản, chúng ta sẽ chỉ tập trung vào một lớp kiểm thử duy nhất: IngredientRepositoryTest. Lớp này sẽ kiểm thử IngredientRepository, xác minh rằng nó có thể lưu các đối tượng Ingredient, truy vấn một Ingredient đơn lẻ, và truy vấn tất cả các đối tượng Ingredient đã được lưu. Mẫu mã tiếp theo minh họa lớp kiểm thử này.
Liệt kê 13.7 Kiểm thử một repository Spring Data R2DBC
package tacos.data;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import tacos.Ingredient;
import tacos.Ingredient.Type;
@DataR2dbcTest
public class IngredientRepositoryTest {
@Autowired
IngredientRepository ingredientRepo;
@BeforeEach
public void setup() {
Flux<Ingredient> deleteAndInsert = ingredientRepo.deleteAll()
.thenMany(ingredientRepo.saveAll(
Flux.just(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
new Ingredient("CHED", "Cheddar Cheese", Type.CHEESE)
)));
StepVerifier.create(deleteAndInsert)
.expectNextCount(3)
.verifyComplete();
}
@Test
public void shouldSaveAndFetchIngredients() {
StepVerifier.create(ingredientRepo.findAll())
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.consumeRecordedWith(ingredients -> {
assertThat(ingredients).hasSize(3);
assertThat(ingredients).contains(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
assertThat(ingredients).contains(
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
assertThat(ingredients).contains(
new Ingredient("CHED", "Cheddar Cheese", Type.CHEESE));
})
.verifyComplete();
StepVerifier.create(ingredientRepo.findBySlug("FLTO"))
.assertNext(ingredient -> {
ingredient.equals(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
});
}
}Phương thức shouldSaveAndFetchIngredients() bắt đầu bằng cách tạo ra một Flux của các đối tượng Ingredient thử nghiệm. Từ Flux này, nó sử dụng toán tử flatMap() để lưu từng Ingredient thông qua phương thức save() trên IngredientRepository đã được inject. Lời gọi subscribe() mở luồng dữ liệu thông qua Flux, dẫn đến việc các đối tượng Ingredient được lưu.
Tiếp theo, một StepVerifier được tạo từ Mono<Ingredient> trả về bởi phương thức findBySlug() của repository. Mono<Ingredient> này nên chứa một Ingredient duy nhất, và đó là điều mà phương thức assertNext() xác minh, so khớp nó với các giá trị mong đợi cho một Ingredient có slug là "FLTO". Sau đó, nó xác nhận rằng Mono đã hoàn thành.
Cuối cùng, một StepVerifier khác được tạo từ Flux<Ingredient> trả về bởi phương thức findAll() của repository. Từng cái một, nó xác minh rằng mỗi Ingredient chảy ra từ Flux đó khớp với ba đối tượng Ingredient đã được lưu lúc đầu của phương thức kiểm thử. Và, giống như với StepVerifier trước, một lời gọi đến verifyComplete() xác nhận rằng Mono đã hoàn thành và không còn đối tượng Ingredient nào nữa.
Mặc dù chúng ta chỉ tập trung vào kiểm thử IngredientRepository, các kỹ thuật tương tự có thể được sử dụng để kiểm thử bất kỳ repository nào được tạo bởi Spring Data R2DBC.
Mọi thứ đến đây đều ổn. Chúng ta đã định nghĩa các kiểu miền (domain types) và repository tương ứng. Và chúng ta đã viết một bài kiểm thử để xác minh rằng chúng hoạt động đúng. Chúng ta có thể sử dụng chúng như hiện tại nếu muốn. Tuy nhiên, các repository này khiến cho việc lưu một TacoOrder trở nên bất tiện vì chúng ta phải tạo và lưu các đối tượng Taco là thành phần của đơn hàng đó trước, rồi sau đó mới lưu đối tượng TacoOrder tham chiếu đến các đối tượng Taco con. Và khi đọc TacoOrder, chúng ta sẽ chỉ nhận được một tập hợp các ID của Taco chứ không phải các đối tượng Taco đã được định nghĩa đầy đủ.
Sẽ thật tuyệt nếu chúng ta có thể lưu TacoOrder như một aggregate root và các đối tượng Taco con được lưu kèm theo nó. Tương tự, cũng sẽ rất hay nếu chúng ta có thể truy vấn một TacoOrder và nhận được một đối tượng được định nghĩa đầy đủ với các đối tượng Taco hoàn chỉnh chứ không chỉ là các ID. Hãy định nghĩa một lớp ở cấp dịch vụ đứng trước OrderRepository và TacoRepository để mô phỏng hành vi lưu trữ của OrderRepository trong chương 3.
