# Nội dung chi tiết

## 1. Nguyên tắc Unit Test API

Mỗi API cần test các nhóm case cơ bản:

1. Validate input
2. Auth / permission
3. Business logic
4. Repository interaction
5. Exception handling
6. Logging
7. HTTP status &amp; response

---

# 2. Ví dụ API mẫu

Controller:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40restcontroller-%40req"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@RestController</span><span class="hljs-meta">@RequestMapping("/users")</span><span class="hljs-meta">@RequiredArgsConstructor</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserService userService;    <span class="hljs-meta">@PostMapping</span>    <span class="hljs-keyword">public</span> ResponseEntity<UserResponse> <span class="hljs-title function_">create</span><span class="hljs-params">(            </span><span class="hljs-meta">@Valid</span> <span class="hljs-meta">@RequestBody</span> CreateUserRequest req) {        <span class="hljs-keyword">return</span> ResponseEntity.ok(userService.create(req));    }}`</div></div>DTO:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40data-public-class-c"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Data</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CreateUserRequest</span> {    <span class="hljs-meta">@NotBlank</span>    <span class="hljs-keyword">private</span> String username;    <span class="hljs-meta">@Email</span>    <span class="hljs-keyword">private</span> String email;}`</div></div>Service:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40service-%40requiredar"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Service</span><span class="hljs-meta">@RequiredArgsConstructor</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService</span> {    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserRepository repo;    <span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(CreateUserRequest req)</span> {        <span class="hljs-type">User</span> <span class="hljs-variable">u</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(req.getUsername(), req.getEmail());        repo.save(u);        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserResponse</span>(u.getId(), u.getUsername());    }}`</div></div>---

# 3. Cấu trúc test chuẩn

Dependency:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%3Cdependency%3E-%3Cgroupi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-tag"><<span class="hljs-name">dependency</span></span>>  <span class="hljs-tag"><<span class="hljs-name">groupId</span></span>>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span></span>>  <span class="hljs-tag"><<span class="hljs-name">artifactId</span></span>>spring-boot-starter-test<span class="hljs-tag"></<span class="hljs-name">artifactId</span></span>>  <span class="hljs-tag"><<span class="hljs-name">scope</span></span>>test<span class="hljs-tag"></<span class="hljs-name">scope</span></span>><span class="hljs-tag"></<span class="hljs-name">dependency</span></span>>`</div></div>Controller test dùng:

- @WebMvcTest
- MockMvc
- @MockBean

---

# 4. Test Validate Input

Case cần có:

- thiếu field bắt buộc
- email sai format
- request null

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40webmvctest%28usercont"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@WebMvcTest(UserController.class)</span><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserControllerValidationTest</span> {    <span class="hljs-meta">@Autowired</span>    <span class="hljs-keyword">private</span> MockMvc mockMvc;    <span class="hljs-meta">@MockBean</span>    <span class="hljs-keyword">private</span> UserService userService;    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldFail_whenUsernameBlank</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {        <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""            {"username":"","email":"a@test.com"}        """</span>;        mockMvc.perform(post(<span class="hljs-string">"/users"</span>)                .contentType(MediaType.APPLICATION_JSON)                .content(json))                .andExpect(status().isBadRequest());    }    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldFail_whenEmailInvalid</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {        <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""            {"username":"john","email":"invalid"}        """</span>;        mockMvc.perform(post(<span class="hljs-string">"/users"</span>)                .contentType(MediaType.APPLICATION_JSON)                .content(json))                .andExpect(status().isBadRequest());    }}`</div></div>---

# 5. Test Auth / Permission

Giả sử API cần login:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40withmockuser-%40test-"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@WithMockUser</span><span class="hljs-meta">@Test</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldSuccess_whenAuthenticated</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {    <span class="hljs-keyword">when</span>(userService.create(any()))        .thenReturn(<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserResponse</span>(<span class="hljs-number">1L</span>,<span class="hljs-string">"john"</span>));    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""        {"username":"john","email":"a@test.com"}    """</span>;    mockMvc.perform(post(<span class="hljs-string">"/users"</span>)            .contentType(MediaType.APPLICATION_JSON)            .content(json))            .andExpect(status().isOk());}`</div></div>Test chưa login:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40test-void-create_sh"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Test</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldUnauthorized_whenNoAuth</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""        {"username":"john","email":"a@test.com"}    """</span>;    mockMvc.perform(post(<span class="hljs-string">"/users"</span>)            .contentType(MediaType.APPLICATION_JSON)            .content(json))            .andExpect(status().isUnauthorized());}`</div></div>---

# 6. Test Business Logic (Service)

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40extendwith%28mockitoe"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@ExtendWith(MockitoExtension.class)</span><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserServiceTest</span> {    <span class="hljs-meta">@Mock</span>    <span class="hljs-keyword">private</span> UserRepository repo;    <span class="hljs-meta">@InjectMocks</span>    <span class="hljs-keyword">private</span> UserService service;    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldSaveUser</span><span class="hljs-params">()</span> {        <span class="hljs-type">CreateUserRequest</span> <span class="hljs-variable">req</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateUserRequest</span>();        req.setUsername(<span class="hljs-string">"john"</span>);        req.setEmail(<span class="hljs-string">"a@test.com"</span>);        <span class="hljs-keyword">when</span>(repo.save(any())).thenAnswer(i -> i.getArgument(<span class="hljs-number">0</span>));        <span class="hljs-type">UserResponse</span> <span class="hljs-variable">res</span> <span class="hljs-operator">=</span> service.create(req);        assertEquals(<span class="hljs-string">"john"</span>, res.getUsername());        verify(repo).save(any());    }}`</div></div>---

# 7. Test Exception Handling

Giả sử email trùng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-when%28repo.save%28any%28%29"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">when</span>(repo.save(any()))    .thenThrow(<span class="hljs-keyword">new</span> <span class="hljs-title class_">DataIntegrityViolationException</span>(<span class="hljs-string">"dup"</span>));`</div></div>Test:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40test-void-create_sh-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Test</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldThrow_whenDuplicateEmail</span><span class="hljs-params">()</span> {    <span class="hljs-type">CreateUserRequest</span> <span class="hljs-variable">req</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateUserRequest</span>();    req.setUsername(<span class="hljs-string">"john"</span>);    req.setEmail(<span class="hljs-string">"a@test.com"</span>);    <span class="hljs-keyword">when</span>(repo.save(any()))        .thenThrow(<span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">"duplicate"</span>));    assertThrows(RuntimeException.class,        () -> service.create(req));}`</div></div>---

# 8. Test Logging

Giả sử service có log:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-log.info%28%22create-use"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`log.info(<span class="hljs-string">"Create user {}"</span>, req.getUsername());`</div></div>Test log:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40extendwith%28mockitoe-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@ExtendWith(MockitoExtension.class)</span><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserServiceLogTest</span> {    <span class="hljs-meta">@Mock</span>    <span class="hljs-keyword">private</span> UserRepository repo;    <span class="hljs-meta">@InjectMocks</span>    <span class="hljs-keyword">private</span> UserService service;    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldLog</span><span class="hljs-params">()</span> {        <span class="hljs-type">CreateUserRequest</span> <span class="hljs-variable">req</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateUserRequest</span>();        req.setUsername(<span class="hljs-string">"john"</span>);        req.setEmail(<span class="hljs-string">"a@test.com"</span>);        <span class="hljs-keyword">when</span>(repo.save(any())).thenAnswer(i -> i.getArgument(<span class="hljs-number">0</span>));        service.create(req);        verify(repo).save(any());    }}`</div></div>(Thực tế có thể dùng LogCaptor)

---

# 9. Test HTTP Response

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40test-%40withmockuser-"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Test</span><span class="hljs-meta">@WithMockUser</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldReturnBody</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {    <span class="hljs-keyword">when</span>(userService.create(any()))        .thenReturn(<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserResponse</span>(<span class="hljs-number">1L</span>,<span class="hljs-string">"john"</span>));    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""        {"username":"john","email":"a@test.com"}    """</span>;    mockMvc.perform(post(<span class="hljs-string">"/users"</span>)            .contentType(MediaType.APPLICATION_JSON)            .content(json))            .andExpect(status().isOk())            .andExpect(jsonPath(<span class="hljs-string">"$.username"</span>).value(<span class="hljs-string">"john"</span>));}`</div></div>---

# 10. Danh sách Case chuẩn cho mỗi API

Mỗi API cần tối thiểu:

### Validate

- [ ]  [ ]  thiếu field
- [ ]  [ ]  format sai
- [ ]  [ ]  null body

### Auth

- [ ]  [ ]  chưa login
- [ ]  [ ]  sai role
- [ ]  [ ]  đúng role

### Business

- [ ]  [ ]  flow thành công
- [ ]  [ ]  dữ liệu biên
- [ ]  [ ]  điều kiện đặc biệt

### Repository

- [ ]  [ ]  save OK
- [ ]  [ ]  not found
- [ ]  [ ]  duplicate

### Exception

- [ ]  [ ]  DB lỗi
- [ ]  [ ]  logic lỗi
- [ ]  [ ]  external lỗi

### Response

- [ ]  [ ]  status code
- [ ]  [ ]  body
- [ ]  [ ]  schema

### Log

- [ ]  [ ]  log khi success
- [ ]  [ ]  log khi error

---

# 11. Checklist Unit Test API

- [ ]  [ ]  Test validate
- [ ]  [ ]  Test auth
- [ ]  [ ]  Test service
- [ ]  [ ]  Mock repo
- [ ]  [ ]  Test exception
- [ ]  [ ]  Test response
- [ ]  [ ]  Verify interaction
- [ ]  [ ]  No DB thật
- [ ]  [ ]  No network