Mockito Answer API:根據參數傳回值
一、簡介
模擬是一種以具有預定義行為的物件取代真實組件的測試技術。這使得開發人員可以隔離和測試特定元件,而無需依賴依賴項。模擬是具有方法呼叫的預先定義答案的對象,它也有執行的期望。
在本教程中,我們將了解如何借助 Mockito 提供的 Answer API,使用基於不同角色的 Mockito 來測試基於使用者角色的身份驗證服務。
2.Maven依賴
在進入本文之前,加入Mockito 依賴項是必不可少的。
<dependency>
 <groupId>org.mockito</groupId>
 <artifactId>mockito-core</artifactId>
 <version>5.14.2</version>
 <scope>test</scope>
 </dependency>讓我們新增JUnit 5依賴項,因為我們的單元測試的某些部分需要它。
<dependency>
 <groupId>org.junit.jupiter</groupId>
 <artifactId>junit-jupiter-engine</artifactId>
 <version>5.11.3</version>
 <scope>test</scope>
 </dependency>3. Answer API介紹
Answer API 允許我們透過指定模擬方法在呼叫時應傳回的內容來自訂模擬方法的行為。當我們希望模擬根據輸入參數提供不同的響應時,這將很有用。讓我們探索更多主題,以更清晰地理解概念來建構我們的主題。
Mockito 中的Answer API 攔截模擬物件上的方法調用,並將這些調用重定向到自訂行為。此過程涉及允許 Mockito 模擬複雜行為而無需修改底層程式碼的內部步驟。讓我們詳細探討這一點,從模擬建立到方法呼叫攔截。
3.1.使用thenAnswer()進行模擬建立和存根
Mockito 中的模擬建立從mock()方法開始,該方法使用CGLib 或Reflection API 產生目標類別的代理程式。然後在內部註冊該代理,使 Mockito 能夠管理其生命週期。
建立模擬後,我們繼續進行方法存根,定義方法的特定行為。 Mockito 攔截此調用,識別目標方法和參數,並使用thenAnswer()方法設定自訂回應。 thenAnswer()方法採用介面實現,允許我們指定自訂行為:
// Mocking an OrderService class
 OrderService orderService = mock(OrderService.class);
 // Stubbing processOrder method
 when(orderService.processOrder(anyString())).thenAnswer(invocation -> {
 String orderId = invocation.getArgument(0);
 return "Order " + orderId + " processed successfully";
 });
 // Using the stubbed method
 String result = orderService.processOrder("12345");
 System.out.println(result); // Output: Order 12345 processed successfully
這裡, processOrder()被存根以傳回帶有訂單 ID 的訊息。呼叫時,Mockito 會攔截並套用自訂Answer邏輯。
3.2.方法呼叫攔截
了解 Mockito 的Answer API 的工作原理對於在測試中設定靈活的行為至關重要。讓我們分解一下在測試執行期間在模擬上呼叫方法時發生的內部流程:
- 當在模擬上呼叫方法時,該呼叫將透過代理實例重定向到 Mockito 的內部處理機制。
-   Mockito 檢查是否已為該方法註冊了行為。它使用方法簽名來查找適當的Answer實作。
-   如果找到Answer實現,則有關傳遞給方法的參數、方法簽名以及對模擬物件的引用的資訊將儲存在InvocationOnMock類別的實例中。
-   使用InvocationOnMock,我們可以使用getArgument(int index)存取方法參數來動態控制方法的行為
此內部流程使Answer API 能夠根據上下文動態回應。考慮一個內容管理系統,其中使用者權限因角色而異。我們可以使用Answer API 來動態模擬授權,具體取決於使用者的角色和請求的操作。讓我們看看如何在以下部分中實現這一點。
4. 建立User和Action模型
由於我們將使用內容管理系統作為範例,因此我們將有四個角色:管理員、編輯者、檢視者和訪客。這些角色將作為不同CRUD操作的基本授權。管理員可以執行所有操作,編輯者可以建立、讀取和更新,查看者只能讀取內容,訪客無權執行任何操作。讓我們先建立一個User類別:
public class CmsUser {
 private String username;
 private String role;
 public CmsUser(String username, String role) {
 this.username = username;
 this.role = role;
 }
 public String getRole() {
 return this.role;
 }
 }現在,讓我們使用ActionEnum類別定義一個枚舉來捕捉可能的 CRUD 操作:
public enum ActionEnum {
 CREATE, READ, UPDATE, DELETE;
 }定義了ActionEnum後,我們就可以開始服務層了。讓我們先定義AuthorizationService介面。此介接將包含一個方法來檢查使用者是否可以執行特定的 CRUD 操作:
public interface AuthorizationService {
 boolean authorize(CmsUser user, ActionEnum actionEnum);
 }此方法將傳回給定的CmsUser是否有資格執行給定的 CRUD 操作。現在我們已經完成了這些,我們可以繼續查看Answer API 的實際實作。
5. 為AuthorizationService建立測試
我們首先建立AuthorizationService介面的模擬版本:
@Mock
 private AuthorizationService authorizationService;現在,讓我們建立一個設定方法來初始化模擬並為AuthorizationService中的authorize()方法定義預設行為,允許它根據角色模擬不同的使用者權限:
@Before
 public void setup() {
 MockitoAnnotations.initMocks(this);
 when(this.authorizationService.authorize(any(CmsUser.class), any(ActionEnum.class)))
 .thenAnswer(invocation -> {
 CmsUser user = invocation.getArgument(0);
 ActionEnum action = invocation.getArgument(1);
 switch(user.getRole()) {
 case "ADMIN": return true;
 case "EDITOR": return action != ActionEnum.DELETE;
 case "VIEWER": return action == ActionEnum.READ;
 case "GUEST":
 default: return false;
 }
 });
 }在此設定方法中,我們初始化測試類別的模擬,這會在每次測試運行之前準備好任何模擬的依賴項以供使用。接下來,我們使用when(this.authorizationService.authorize(…)).thenAnswer(…)在authorizationService模擬中定義authorize()方法的行為。此設定指定一個自訂答案:每當使用任何CmsUser和任何ActionEnum呼叫authorize()方法時,它都會根據使用者的角色進行回應。
為了驗證正確性,我們可以從程式碼儲存庫執行給givenRoles_whenInvokingAuthorizationService_thenReturnExpectedResults() 。
6. 驗證我們的實施
現在我們已經完成了所有工作,讓我們建立測試方法來驗證我們的實作。
@Test
 public void givenRoles_whenInvokingAuthorizationService_thenReturnExpectedResults() {
 CmsUser adminUser = createCmsUser("Admin User", "ADMIN");
 CmsUser guestUser = createCmsUser("Guest User", "GUEST");
 CmsUser editorUser = createCmsUser("Editor User", "EDITOR");
 CmsUser viewerUser = createCmsUser("Viewer User", "VIEWER");
 verifyAdminUserAccess(adminUser);
 verifyEditorUserAccess(editorUser);
 verifyViewerUserAccess(viewerUser);
 verifyGuestUserAccess(guestUser);
 }讓我們專注於其中一種驗證方法,以保持文章的簡潔性。我們將透過實作來驗證管理員使用者的存取權限,並且我們可以參考程式碼儲存庫來了解其他使用者角色的實作。
我們首先為不同的角色建立CmsUser實例。然後,我們呼叫verifyAdminUserAccess()方法,並將 adminUser 實例作為參數傳遞。在verifyAdminUserAccess()方法中,我們迭代所有ActionEnum值並斷言管理員使用者有權存取所有這些值。這將驗證授權服務是否正確授予管理員使用者對所有操作的完全存取權。實作其他使用者角色驗證方法遵循類似的模式,如果需要進一步了解,我們可以在程式碼儲存庫中探索這些方法。
七、結論
在本文中,我們研究如何使用 Mockito 的Answer API 在模擬測試中動態實作基於角色的授權邏輯。透過為使用者設定基於角色的訪問,我們展示瞭如何根據特定參數的屬性傳回不同的回應。這種方法增強了程式碼覆蓋率並最大限度地減少了不可預見的失敗的可能性,使我們的測試更加可靠和有效。
與往常一樣,這些範例的完整實作可以在 GitHub 上找到。