Trong bài viết này mình sẽ hướng dẫn các bạn viết Unit Test tầng Service của Spring Boot.
Trong Spring để sử dụng được môi trường test ta cần cung cấp các thư viện
testCompile group: 'com.h2database', name: 'h2', version: '1.4.200'
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.2.0.RELEASE'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
H2 là một cơ sở dữ liệu quan hệ (Relational database) được phát triển bởi công ty H2 Software. H2 được viết bằng ngôn ngữ Java, mã nguồn mở, gọn nhẹ, miễn phí. H2 phát hành phiên bản đầu tiên vào năm 2005. Bộ cài đặt H2 có dung lượng rất nhỏ, chỉ khoảng 8MB. Đây là một in memory database. Đại khái là mọi thứ bạn lưu trên database chỉ tồn tại trong bộ nhớ, khi tắt ứng dụng, database sẽ mất sạch, thích hợp cho môi trường test. Thư viện com.h2database chỉ được include vào dự án khi Compile dự án ở môi trường test.
Trong Spring Boot, cấu trúc thư mục trong test nên giống hoàn toàn với cấu trúc thư mục trong main của chúng ta.

Tầng Service là tầng xử lý nghiệp vụ trong Spring. Tầng này dùng dữ liệu lấy được từ tầng Repository. Vì vậy để đảm bảo test chạy độc lập với môi trường phát triển, khi test, chúng ta cần làm giả tầng Repository.
Chúng ta bắt đầu vào viết test các bạn nhé.
Đây là CategoryService của mình
@Service
public class CategoryService implements ICategoryService {
@Autowired
private ICategoryRepository categoryRepository;
public CategoryService() {
}
public CategoryService(ICategoryRepository categoryRepository) {
this.categoryRepository = categoryRepository;
}
@Override
public Iterable<Categories> findAll() {
return categoryRepository.findAll();
}
@Override
public Optional<Categories> findById(Long id) {
return categoryRepository.findById(id);
}
@Override
public Categories save(Categories categories) {
return categoryRepository.save(categories);
}
@Override
public void remove(Long id) {
categoryRepository.deleteById(id);
}
@Override
public Optional<Categories> findByName(String name) {
return categoryRepository.findByName(name);
}
@Override
public Iterable<Categories> findAllByOutComes(Outcomes outcome) {
return categoryRepository.findAllByOutComes(outcome);
}
}
Chúng ta sẽ tạo ra class CategoryServiceTest.
Các bạn thấy là mình sử dụng ICategoryRepository trong service. Vì vậy chúng ta sẽ phải làm giả CategoryRepository bằng cách sử dụng org.mockito.Mockito;
private ICategoryRepository categoryRepository = Mockito.mock(ICategoryRepository.class);
Tư tưởng của Mock đơn giản là khi muốn test (A gọi B) thì thay vì tạo ra một đối tượng B thật sự, bạn tạo ra một thằng B’ giả mạo, có đầy đủ chức năng như B thật (nhưng không phải thật).
Bạn sẽ giả lập cho B’ biết là khi thằng A gọi tới nó, nó cần làm gì, trả lại cái gì (hardcode). Miễn làm sao cho nó trả ra đúng cái thằng A cần để chúng ta có thể test A thuận lợi nhất. Có như vậy môi trường test của chúng ta mới độc lập được với môi trường phát triển.
Cách mình làm giả CategoryRepositor ở đây là mình sử dụng Mockito.mock()để tạo ra một object của Class bạn yêu cầu, nó thậm chí còn không quan tâm hàm khởi tạo của Class ý như nào và ra sao, vì nó đâu có thật. Nếu các bạn muốn dùng cách khác có thể tìm hiểu thêm @Mock hoặc Mock Init. Trong CategoryService mình còn sử dụng OutcomeRepository nữa nên mình là tương tự với CategoryRepository nhé.
Sau khi làm giả được CategoryRepository chúng ta tiêm nó vào trong hàm khởi tạo của CategoryService
private ICategoryService categoryService = new CategoryService(categoryRepository);
Mình test phương thức findAll() trước các bạn nhé. Chúng ta sử dụng @Test để đánh dấu phương thức Test nhé.
@Test
@DisplayName("findAll can return list is not null")
public void whenFindAllNotNull() {
//////...
}
Giờ mình sẽ tạo ra 1 list Category và nói cho Repo giả vừa tạo rằng khi gọi phương thức findAll của Repo sẽ trả về list đó:
Đoạn mã này mình tạo Outcome giả để mình test
Outcomes outcomes = new Outcomes();
outcomes.setTitle("PHẦN 1: KIẾN THỨC VÀ KỸ NĂNG LẬP TRÌNH");
doReturn(Optional.of(outcomes)).when(outcomesRepository).findById(1L);
List<Outcomes> outcomesIterable = Arrays.asList(outcomes);
doReturn(outcomesIterable).when(outcomesRepository).findAll();
Phần này mình xác định dùng lại nhiều lần ở các Test nên mình sử đặt nó trong @BeforeEach nghĩa là những lệnh chạy trong phương thức này sẽ được chạy lại trước mỗi Test.
Categories category = new Categories();
category.setCategoryId("1.1");
category.setName("Lập trình cơ bản");
doReturn(Optional.of(category)).when(categoryRepository).findById(1L);
doReturn(Optional.of(category)).when(categoryRepository).findByName("Lập trình cơ bản");
List<Categories> categoriesList = Arrays.asList(category);
doReturn(categoriesList).when(categoryRepository).findAll();
Phương thức findAll() mình test 2 việc, 1 là nó trả về 1 list không phải list trống, 2 là kích thước của list được trả về là 1, nếu các bạn muốn có thể thay đổi kích thước này trong phần chuẩn bị dữ liệu. Mình hơi lười nên thế thôi vậy. Ở đây mình phải viết 2 phương thức Test các bạn nhé.
@Test
@DisplayName("findAll can return list is not null")
public void whenFindAllNotNull() {
assertThat(categoryService.findAll()).isNotNull();
}
@Test
@DisplayName("findAll can return a list has 1 element")
public void whenfindAll_thenReturnListHasOneElement() {
Iterable<Categories> categories = categoryService.findAll();
assertThat(categories).hasSize(1);
}
Tiếp tục mình test phương thức findById, trong phần chuẩn bị dữ liệu test mình báo cho repo biết là khi gọi phương thức findById với tham số 1L nó sẽ trả về category nào? như sau:
doReturn(Optional.of(category)).when(categoryRepository).findById(1L);
Với phương thức này mình test 2 việc, 1 là mình test trường hợp tìm thấy category tương ứng khi truyền tham số 1L, 2 là mình test trường hợp không tìm thấy category khi truyền tham số 2L.
@Test
@DisplayName("findbyID return category name Lap trinh co ban")
public void whenfindById_thenReturnCategory() {
String name = "Lập trình cơ bản";
Optional<Categories> categories = categoryService.findById(1L);
assertThat(categories.get().getName()).isEqualTo(name);
}
@Test
@DisplayName("findbyID 2L return isPresent")
public void whenfindById_thenReturnCategoryisPresent() {
Optional<Categories> categories = categoryService.findById(2L);
assertThat(categories.isPresent()).isFalse();
}
Tương tự với các phương thức khác, các bạn có thể tham khảo mã test tại đây.
Vậy là mình đã hướng dẫn các bạn viết Test với module Service trong Spring Boot. Bài sau mình sẽ hướng dẫn các bạn viết Test Controller các bạn nhé. <3

Leave a Reply