轉載:https://unmi.cc/mockito-how-to-mock-void-method/#more-7748
最初接觸 Mockito 還思考並嘗試過如何用它來 mock 返回值為 void 的方法,然而 Google 查找到的一般都會說用 doThrow()
的辦法
doThrow(new RuntimeException()).when(mockObject).methodWithVoidReturn();
因為無法使用常規的 when(mockObject.foo()).thenReturn(...)
的方法。
當時我就納悶,為何我想 mock 一個返回值為 void 的方法,卻是在模擬拋出一個異常,現在想來如果一個返回值為 void 的方法,為何要去 mock 這個方法呢?
回想一個我們要 mock 一個方法的意圖是什么:
- 在特定輸入參數的情況下期待需要的輸出結果(返回值)
- 在方法拋出某種類型異常調用者作出的反應
對於 void 返回值的方法,如果要驗證有沒有被調用過幾次可以在事后用 verify()
方法去斷言。所以基本上對於 void 返回值的方法一般可不用去 mock 它,只需用 verify() 去驗證,或者就是像前面一樣模擬出現異常時的情況。
所以本文並不像是去直接回答標題所示的問題: Mockito 如何 mock 返回值為 void 的方法,而是如何應對 mock 對象的 void 方法
一般不用對 void 方法打樁, 事后 verify 就行
測試代碼針對 mock 對象的 void 方法調用本來就沒有什么效果,所以一般也無須用 doNothing()
, 況且 void 提供不了返回值作進一步 mock,只需要在事后用 verify()
進行驗證一下。
例如有下面的代碼
類 UserDao
1
2
3
|
public interface UserDao {
void save(User user);
}
|
類 UserService
1
2
3
4
5
6
7
8
9
10
11
|
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser(User user) {
userDao.save(user);
}
}
|
要寫一個針對 UserService.saveUser(User user)
的測試方法
1
2
3
4
5
6
7
8
9
10
|
@Test
public void shouldCallUserDaoSaveMethod() {
UserDao userDao = Mockito.mock(UserDao.class);
UserService userService = new UserService(userDao);
User user = new User(1, "Yanbin");
userService.saveUser(user);
verify(userDao, times(1)).save(user);
}
|
模擬 void 方法拋出異常時調用者的響應
這就是為什么 Google 上那么多答案告訴我們要用到 doThrow()
的原因,假如我們的 UserService.saveUser(User user)
方法在 userDao
報出 DataAccessException 時轉換為 ApplicationException
異常, 它的代碼實現如下:
1
2
3
4
5
6
7
8
9
|
//UserService 的 saveUser(User user) 方法修改如下
public void saveUser(User user) {
try (
userDao.save(user);
catch(DataAccessException dae) {
throw new ApplicationException(dae, "can't save user due to issue reported from database");
}
}
|
要測試上面那個異常處理過程,我們可以寫下面的測試
1
2
3
4
5
6
7
8
9
10
|
@Test(expected = ApplicationException.class)
public void raiseApplicationExceptionIfDataAccessExceptionOccursFromUserDao() {
UserDao userDao = Mockito.mock(UserDao.class);
UserService userService = new UserService(userDao);
User user = new User(1, "Yanbin");
doThrow(new DataAccessException()).when(userDao).save(user);
userService.saveUser(user);
}
|
上面的 doThrow()
行寫成下面的方式也是一樣的
doNothing().doThrow(new DataAccessException()).when(userDao).save(user);
Mockito 在調用 mock 對象的 void 方法時本來默認就是 doNothing
. 那什么時候需要用到 doNothing()
呢?就是下面的情況
部分 mock 時 void 方法可 doNothing
除了 @Mock
或 Mockito.mock(Class<T>)
來創建 mock 對象,還可以使用 spy(realObject)
或 spy(Class)
來獲得 mock 對象,這兩種方式得到的 mock 對象是不同的。前者所有的方法都被 mock, 后者(spy) 未打樁的方法會調用被 spy 對象的實際實現。
看下面的例子如何對 spy 對象的方法進行 mock 的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Test
public void partialMockSpiedObject() {
List<String> names = new ArrayList<>();
List<String> spy = spy(names);
doNothing().when(spy).add(anyInt(), anyString());
spy.add("Yanbin");
spy.add(0, "Unmi");
assertEquals(1, spy.size());
assertEquals("Yanbin", spy.get(0));
assertEquals(0, names.size());
}
|
上面的測試可成功通過,我們用 doNothing().when(spy).add(anyInt(), anyString())
讓帶下標的方式添加元素什么也不做,而未被打樁的方法會調用 names
的實際實現。因此我們可以看到上面的斷言結果。
doNothing()
還真不是 doNothing()
, 在搭配 spy 作部分 mock 時還是很有用的,如下面的測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class Handler {
private UserDao userDao;
void initialize() {
userDao = new UserDao();
}
public void handle(Event event) {
if(event != null) {
initialize();
}
......
}
}
//test code
@Test
public void testHandleEvent() {
Handler handler = spy(new Handler()); //spy 會是部分 mock, 只有 stub 了方法才會被 mock, 其余調用實際方法
//放心的用反射去修改 userDao 的值,因為不會被 initialize() 方法覆蓋
UserDao mockedUserDao = Mockito.mock(UserDao);
setFieldValueOfUserDaoInReflection(handler, mockedUserDao);
doNothing().when(handler).initialize(); //因為它 doNothing, 保證了 handler 仍然持有 mockedUserDao
when(mockedUserDao.findUsers()).thenReturn(users);
......
}
|
另外如果還有其他情況可考慮 doCallRealMethod() 或 doAnser()
doCallRealMethod().when(mockObject).voidMethod();
doAnswer(answer).when(mockObject).voidMethod();
相關鏈接:
注(2017-09-19):如果用 spy
進行部分 mock 的時候,和常規方法 mock 略有不同,比如要先申請 doReturn(...)
1
2
3
4
5
6
7
8
|
UserService service = new UserService();
UserService spyService = spy(service);
doReturn("Yanbin").when(spyService).findNameById(123);
// spy 部分 mock 不能下面那樣
// when(spyService.findNameById(123)).thenReturn("Yanbin");
// 否則 when(..) 打樁的時候就會執行實際的方法 service.findNameById(123),這是我們不希望的
|
再次說明,spy 打樁的方法被 Mock, 未打樁的會調用實際方法。