JUnit5环境下如何使用Mockito模拟被测类中的无返回值(Void)方法
Let's break down how to handle void method mocking in Mockito, especially tailored to your customer service code.
First, you’re already on the right track with doNothing()—this is the standard way to suppress the real implementation of a void method when using a @Spy (since spies default to executing real code unless explicitly told otherwise). Let’s cover all relevant options and validate your current test setup.
1. Basic: Suppress Real Void Method Execution
doNothing() is perfect for your test case where you want to verify deleteCustomer is called without running the actual deletion logic (which would trigger downstream service calls like entityViewService.unassignCustomerEntityViews). Your current code for this is correct:
doNothing() .when(customerService) .deleteCustomer(any(), any());
2. Verify Calls & Capture Arguments
Your use of ArgumentCaptor to validate the CustomerId passed to deleteCustomer is a great practice. Let’s confirm this setup works as intended:
- You capture the argument with:
ArgumentCaptor<CustomerId> customerIdArgumentCaptor = ArgumentCaptor.forClass(CustomerId.class); - Then verify the method was called once and validate the captured argument:
then(customerService) .should(times(1)) .deleteCustomer(any(TenantId.class), customerIdArgumentCaptor.capture()); assertEquals(CUSTOMER_ID, customerIdArgumentCaptor.getValue());
This correctly checks that the expected CustomerId is passed when deleteCustomersByTenantId runs.
3. Advanced: Custom Logic for Void Methods
If you need more than just suppressing the method (e.g., log parameters, modify input objects, or trigger side effects), use doAnswer() even for void methods. For example:
doAnswer(invocation -> { // Extract arguments passed to deleteCustomer TenantId tenantId = invocation.getArgument(0); CustomerId customerId = invocation.getArgument(1); // Add custom logic here System.out.printf("Delete requested for tenant %s and customer %s%n", tenantId, customerId); // Void methods require returning null return null; }) .when(customerService) .deleteCustomer(any(TenantId.class), any(CustomerId.class));
4. Simulate Exceptions from Void Methods
To test error handling (e.g., how your code reacts when deleteCustomer throws a ThingsboardException), use doThrow():
doThrow(new ThingsboardException("Test exception")) .when(customerService) .deleteCustomer(any(TenantId.class), any(CustomerId.class));
Why Your Current Setup Works
In your code, the internal customersByTenantRemover calls deleteCustomer for each found customer. Using doNothing() on your spy'd customerService prevents the real deleteCustomer logic from executing, keeping your test focused on verifying the flow of deleteCustomersByTenantId rather than downstream deletion steps.
Your test already correctly validates:
customerDao.findCustomersByTenantIdis called oncedeleteCustomeris called once with the expectedCustomerId
This is a solid, focused test setup for your use case.
内容的提问来源于stack exchange,提问作者sobhan nami

