這邊針對商品部分 Service 寫一些單元測試,下面先列出預計測試的名稱,主要根據實際方法內會出現判斷的條件去設計,嘗試讓每個 Service 單元測試都 100%。
先注入並且初始化我們需要用到的Bean,這邊主要測試 ProductService
,所以加上 @InjectMocks
註解,其他應用到的 Dao 都用 @Mock
標住用 mock 產生虛擬元件注入有標住 @InjectMocks
的 ProductService,@BeforeEach 先初始化待會會測試用到的一些物件資料。
@ExtendWith(MockitoExtension.class)
public class ProductServiceTest {
@Mock
private ProductDao productDao;
@InjectMocks
private ProductService productService;
private Product mockProduct1;
private Product mockProduct2;
private ProductRequest mockProductRequest;
private List<Product> mockProducts;
@BeforeEach
void setUp() {
mockProduct1 = new Product();
mockProduct1.setId(1);
mockProduct1.setProductName("Test Product");
mockProduct1.setUnitPrice(10.0);
mockProduct1.setUnitsInStock(100);
mockProduct1.setDiscontinued(false);
Supplier supplier1 = new Supplier();
supplier1.setId(1);
mockProduct1.setSupplier(supplier1);
mockProduct2 = new Product();
mockProduct2.setId(2);
mockProduct2.setProductName("Test Wireless Mouse");
mockProduct2.setDiscontinued(false);
Supplier supplier2 = new Supplier();
supplier2.setId(2);
mockProduct2.setSupplier(supplier2);
mockProduct2.setUnitPrice(24.99);
mockProduct2.setUnitsInStock(103);
mockProductRequest = new ProductRequest();
mockProductRequest.setProductName("New/Update Product");
mockProductRequest.setUnitPrice(15.0);
mockProductRequest.setUnitsInStock(50);
mockProductRequest.setDiscontinued(false);
mockProductRequest.setSupplier(supplier1);
}
// 取得所有商品
@Test
public void testGetAllProducts() {}
// 取得特定 id 商品
@Test
public void testGetProductById() {}
// 取得特定 id 商品,找不到該 id 之商品
@Test
void testGetProductById_NotFound() {}
// 創建商品
@Test
void testCreateProduct() {}
// 更新商品
@Test
void testUpdateProduct() {}
// 更新商品,找不到該 id 之產品
@Test
void testUpdateProduct_NotFound() {}
// 刪除商品
@Test
void testDeleteProduct() {}
// 搜尋產品並排序
@Test
void testSearchAndSortProducts() {}
// 搜尋產品並排序,傳入商品名參數為空
@Test
void testSearchAndSortProducts_NullOrEmptyProductName() {}
}
關於基本查詢相關
testGetAllProducts
testGetProductById
testGetProductById_NotFound
@Test
void testSearchAndSortProducts() {
mockProducts = Arrays.asList(mockProduct2, mockProduct1);
Page<Product> productPage = new PageImpl<>(mockProducts);
when(productDao.findByProductNameContainingIgnoreCase(eq("Test"), any(PageRequest.class)))
.thenReturn(productPage);
List<Product> result = productService.searchAndSortProducts("Test", "id", "desc", 0, 10);
assertEquals(2, result.size());
assertEquals(mockProduct2, result.get(0));
assertEquals(mockProduct1, result.get(1));
verify(productDao).findByProductNameContainingIgnoreCase(eq("Test"), any(PageRequest.class));
}
@Test
void testSearchAndSortProducts_NullOrEmptyProductName() {
mockProducts = Arrays.asList(mockProduct1, mockProduct2);
Page<Product> productPage = new PageImpl<>(mockProducts);
when(productDao.findAll(any(PageRequest.class))).thenReturn(productPage);
// null product name
List<Product> resultNull = productService.searchAndSortProducts(null, "id", "asc", 0, 10);
assertEquals(2, resultNull.size());
assertEquals(mockProduct1, resultNull.get(0));
assertEquals(mockProduct2, resultNull.get(1));
// empty product name
List<Product> resultEmpty = productService.searchAndSortProducts("", "id", "asc", 0, 10);
assertEquals(2, resultEmpty.size());
assertEquals(mockProduct1, resultEmpty.get(0));
assertEquals(mockProduct2, resultNull.get(1));
verify(productDao, times(2)).findAll(any(PageRequest.class));
}
關於新增、刪除、修改相關
testCreateProduct
testUpdateProduct
testUpdateProduct_NotFound
testDeleteProduct
@Test
void testCreateProduct() {
Product newMockProduct = productService.convertToModel(mockProductRequest);
when(productDao.save(any(Product.class))).thenReturn(newMockProduct);
Product createdProduct = productService.createProduct(mockProductRequest);
assertNotNull(createdProduct);
assertEquals("New/Update Product", createdProduct.getProductName());
verify(productDao, times(1)).save(any(Product.class));
}
@Test
void testUpdateProduct() {
Product updateMcokProduct = productService.convertToModel(mockProductRequest);
when(productDao.findById(1)).thenReturn(Optional.of(mockProduct1));
when(productDao.save(any(Product.class))).thenReturn(updateMcokProduct);
Product updatedProduct = productService.updateProduct(1, mockProductRequest);
assertNotNull(updatedProduct);
assertEquals("New/Update Product", updatedProduct.getProductName());
verify(productDao, times(1)).findById(1);
verify(productDao, times(1)).save(any(Product.class));
}
@Test
void testUpdateProduct_NotFound() {
when(productDao.findById(3)).thenReturn(Optional.empty());
Product updatedProduct = productService.updateProduct(3, mockProductRequest);
assertNull(updatedProduct);
verify(productDao, times(1)).findById(3);
verify(productDao, never()).save(any(Product.class));
}
@Test
void testDeleteProduct() {
doNothing().when(productDao).deleteById(1);
productService.deleteProductById(1);
verify(productDao, times(1)).deleteById(1);
}
搜尋相關
testSearchAndSortProducts
testSearchAndSortProducts_NullOrEmptyProductName
@Test
public void testGetAllProducts() {
mockProducts = Arrays.asList(mockProduct1, mockProduct2);
when(productDao.findAll()).thenReturn(mockProducts);
List<Product> products = productService.getAllProducts();
assertEquals(products.get(0).getProductName(), "Test Product");
assertEquals(products.get(1).getProductName(), "Test Wireless Mouse");
assertTrue(products.size() == 2);
verify(productDao, times(1)).findAll();
}
@Test
public void testGetProductById() {
when(productDao.findById(1)).thenReturn(Optional.of(mockProduct1));
Optional<Product> product = productService.getProductById(1);
assertTrue(product.isPresent());
assertEquals(product.get().getProductName(), "Test Product");
assertEquals(product.get().getUnitPrice(), 10.0);
assertEquals(product.get().getUnitsInStock(), 100);
verify(productDao, times(1)).findById(1);
}
@Test
void testGetProductById_NotFound() {
when(productDao.findById(3)).thenReturn(Optional.empty());
Optional<Product> product = productService.getProductById(3);
assertFalse(product.isPresent());
verify(productDao, times(1)).findById(3);
}
再來針對新建訂單部分 Service 的單元測試
盡量讓內部邏輯可以都被測驗到,每個方法裡的判斷都有走過一遍,這樣測試覆蓋率高也能確保程式運作正常。
先注入並且初始化我們需要用到的Bean,這邊主要測試 OrderService
,所以加上 @InjectMocks
註解,其他應用到的 Dao 都用 @Mock
標住用 mock 產生虛擬元件注入有標住 @InjectMocks
的 OrderService
預計可以拆成下面這些項目:
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {
@InjectMocks
private OrderService orderService;
@Mock
private OrderInfoDao orderInfoDao;
@Mock
private OrderItemDao orderItemDao;
@Mock
private ProductDao productDao;
@Test
void testCreateOrder_Success() {
// 正確創建訂單
}
@Test
void testCreateOrder_NotFoundProduct() {
// 訂單內商品不存在
}
@Test
void testCreateOrder_InsufficientStock() {
// 訂單內商品目前庫存不足
}
}
createOrder_Success
: 測試正確創建訂單
@Test
void testCreateOrder_Success() {
// Arrange
Integer userId = 1;
CreateOrderInfoRequest request = new CreateOrderInfoRequest();
BuyItem buyItem = new BuyItem();
buyItem.setProductId(1);
buyItem.setQuantity(2);
request.setBuyItemList(Arrays.asList(buyItem));
Product product = new Product();
product.setId(1);
product.setProductName("Test Product");
product.setUnitPrice(10.0);
product.setUnitsInStock(5);
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(1);
orderInfo.setUserId(userId);
orderInfo.setTotalAmount(20.0);
OrderItem orderItem = new OrderItem();
orderItem.setId(1);
orderItem.setOrderInfoId(1);
orderItem.setProductId(1);
orderItem.setQuantity(2);
orderItem.setAmount(10.0);
when(productDao.findById(1)).thenReturn(Optional.of(product));
when(orderInfoDao.save(any())).thenReturn(orderInfo);
when(orderItemDao.saveAll(any())).thenReturn(Arrays.asList(orderItem));
CreateOrderResponse response = orderService.createOrder(userId, request);
assertNotNull(response);
verify(productDao, times(1)).save(any());
verify(productDao).save(argThat(savedProduct ->
savedProduct.getId().equals(1) && savedProduct.getUnitsInStock() == 3
));
verify(orderInfoDao, times(1)).save(any());
verify(orderItemDao, times(1)).saveAll(any());
}
createOrder_ProductNotFound
: 測試訂單內商品不存在
ProductDao
返回空的 Optional
@Test
void testCreateOrder_ProductNotFound() {
Integer userId = 1;
CreateOrderInfoRequest request = new CreateOrderInfoRequest();
BuyItem buyItem = new BuyItem();
buyItem.setProductId(1);
buyItem.setQuantity(2);
request.setBuyItemList(Arrays.asList(buyItem));
when(productDao.findById(1)).thenReturn(Optional.empty());
assertThrows(ResponseStatusException.class, () -> orderService.createOrder(userId, request));
}
createOrder_InsufficientStock
: 測試訂單內商品目前庫存不足
@Test
void testCreateOrder_InsufficientStock() {
Integer userId = 1;
CreateOrderInfoRequest request = new CreateOrderInfoRequest();
BuyItem buyItem = new BuyItem();
buyItem.setProductId(1);
buyItem.setQuantity(10);
request.setBuyItemList(Arrays.asList(buyItem));
Product product = new Product();
product.setId(1);
product.setProductName("Test Product");
product.setUnitPrice(10.0);
product.setUnitsInStock(1);
when(productDao.findById(1)).thenReturn(Optional.of(product));
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
() -> orderService.createOrder(userId, request));
assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
}
目前訂單部分測試分享到這邊,針對商品部分因為和先前介紹單元測試那邊類似就沒有多去寫,但開發上可以盡量把重要的功能都涵蓋是最好的。
相關文章也會同步更新我的部落格,有興趣也可以在裡面找其他的技術分享跟資訊。