以下內(nèi)容不會涉及其中原理绸狐,只使用簡單的宵蕉、可能不正確的敘述來幫助理解方法的表現(xiàn)作用享郊。
1. Mockito.mock 和 Mockito.spy 的不同
Mockito.mock 通常的用法是:A obj = Mockito.mock(A.class);
Mockito.spy 通常的用法是:A obj = Mockito.spy(new A());
Mockito.spy()與new的區(qū)別在于晴竞,spy()包裝后的對象养叛,可以使用Mockito.when()种呐、Mockito.verify()對此對象的方法調(diào)用進行設(shè)定與驗證
Mockito.mock() 創(chuàng)建出來的對象,在調(diào)用該對象中的某個方法時弃甥,不會執(zhí)行其它方法爽室。
Mockito.spy() 創(chuàng)建出來的對象,在調(diào)用該對象中的某個方法時,會執(zhí)行其它方法阔墩。
- 結(jié)合示例說明使用效果:
1. UserService service = Mockito.mock(UserServiceImpl.class);
2. service.deleteById(1);
3. UserService service = Mockito.spy(new UserServiceImpl());
4. service.deleteById(1);
class UserService {
public int deleteById(long id) {
User u = this.selectById(id);
if (u != null) {
return this.mapper.deleteById(id);
}
// do something
}
public User selectById(long id) {
// do something
}
}
對于 Mockito.mock 而言
第1行代碼嘿架,使用Mockito.mock()創(chuàng)建出一個UserService對象。
第2行代碼啸箫,對于deleteById()中涉及到的對this.selectById耸彪、this.mapper.deleteById的調(diào)用不會執(zhí)行內(nèi)部的邏輯,且不會有返回值忘苛。
對于 Mockito.spy 而言
第3行代碼蝉娜,使用Mockito.spy()包裝了一下開發(fā)者自己創(chuàng)建的UserService對象。
第4行代碼扎唾,對于deleteById()中涉及到的對this.selectById召川、this.mapper.deleteById的調(diào)用會執(zhí)行內(nèi)部的邏輯。
基于單元測試的基本思想——自掃門前雪
胸遇,被測試的方法
不應(yīng)該因為外部方法的邏輯報錯
而測試失敗荧呐。
因此 Mockito.mock() 是常用的。
但Mockito框架
不能完成對private方法
的測試(被認為具有破壞性)纸镊,而為了達到測試private方法
的目的倍阐,可以使用一個public方法
作為引子,再結(jié)合 Mockito.spy() 完成此目的逗威。
Mockito.spy() 不常用但會用到峰搪,
它引發(fā)的整個方法鏈路的調(diào)用,這破壞了單元測試的基本思想庵楷,但也彌補了 Mockito.mock() 做不到的事情罢艾。
2. @Mock 與 @Spy 的不同
這兩個注解是Mockito.mock 和 Mockito.spy的注解版,可以像使用依賴注入一樣在類中定義為類屬性尽纽。
達到的作用是一樣的咐蚯。
public class UserServiceImplTest {
@Mock / @Spy
UserServiceImpl userService;
}
@InjectMocks
的作用類似于Spring中的@Autowired
@Spy
@InjectMocks
private CommandManager commandManager;
CommandManager 構(gòu)造函數(shù)中需要的參數(shù),會從其它@Mock弄贿、@Spy的對象中自動選取春锋。
3. Mockito.when().thenXX() 的使用(作用:模擬方法調(diào)用)
這個方法對于 Mockito.mock()、Mockito.spy() 沒有區(qū)別差凹,都能起到一樣的作用期奔。
這里還是要基于 標題1 中的示例進行使用說明
class UserService {
public int deleteById(long id) {
User u = this.selectById(id);
if (u != null) {
return this.mapper.deleteById(id);
}
// do something
}
public User selectById(long id) {
// do something
}
}
回到單元測試的基本思想——自掃門前雪
上,
開發(fā)者A測試deleteById()
時危尿,不想關(guān)注外部方法的邏輯呐萌,只想保證內(nèi)部的判斷、步驟的執(zhí)行順序正確谊娇,
但判斷的執(zhí)行條件需要其它方法的返回值(如:u != null),
為了能不受外部方法牽連肺孤、扯后腿,又能夠模擬外部方法的執(zhí)行結(jié)果,就需要用到Mockito.when().thenXX()
- 結(jié)合使用示例進行說明
1. UserService userService = Mockito.mock(UserService.class);
2. Mockito.when(userService.selectById(1)).thenReturn(new User()); // 聲明:當調(diào)用目標方法的行為發(fā)生后赠堵,執(zhí)行指定的return行為小渊。
3. Mockito.when(userService.selectById(2)).thenReturn(new User());
4. userService.deleteById(1);
第4行代碼,在運行到方法內(nèi)部茫叭,需要去執(zhí)行User u = this.selectById(id)
時酬屉,會觸發(fā)第2行代碼聲明的行為(不會觸發(fā)第3行的聲明,因為參數(shù)不對應(yīng))
因此揍愁,靈活地使用Mockito.when().thenXX()
呐萨,對于目標方法的單元測試,就能夠起到忽視外部因素影響的作用吗垮,專心擦自己的屁股垛吗。
when().thenReturn()、when().thenThrow()烁登、when().thenAnswer() 都是類似的效果,
即:當when()聲明的行為發(fā)生后蔚舀,執(zhí)行自定義的then操作
4. Mockito.doXXX().when().xxx() 的使用(作用:模擬方法調(diào)用)
其使用思路與
Mockito.when().thenXX()
一致
Mockito.when().thenXX()
要求目標測試方法
不能使用 void 作為方法返回值
饵沧;
Mockito.doXXX().when().xxx()
沒有這個要求,如果目標測試方法
的返回void赌躺,使用doNothing()即可狼牺。
- 使用示例
1. UserService userService = Mockito.mock(UserService.class);
2. Mockito.doReturn(new User()).when(userService).selectById(1);
3. userService.deleteById(1);
此示例的執(zhí)行效果與 標題3 中的示例,是一樣的執(zhí)行效果礼患。
5. Mockito.anyXX() 的使用(作用:模擬參數(shù))
在上面的 標題3是钥、標題4 的實例中,方法的參數(shù)都是指定的常量缅叠,但系統(tǒng)在上線使用時參數(shù)約等于隨機的悄泥,
因此指定常量作為參數(shù)的測試,是容易有測試盲點的肤粱。
而 Mockito.anyXX()
可以解決這個問題
Mockito.doReturn(new User()).when(userService).selectById(1);
變更為
Mockito.doReturn(new User()).when(userService).selectById(Mock.anyLong());
這樣的變更弹囚,表示只要調(diào)用了selectById
,不管selectById
的傳入?yún)?shù)是什么领曼,都會觸發(fā)之前聲明的行為鸥鹉。
6. Mockito.verify().xxx() 的使用(作用:驗證設(shè)想)
類似于
Assertions.assertXXX()
,只不過是對方法調(diào)用層面的驗證庶骄。
通常用于驗證程序是否按要求退出毁渗、條件判斷是否寫反、條件判斷的邏輯是否如預(yù)期那樣執(zhí)行
单刁。
通常用法為:
Mockito.verify(userService, Mockito.never()).selectById(Mockito.any()); // 目標方法在測試中是否從來沒執(zhí)行
Mockito.verify(userService, Mockito.times(1)).selectById(Mockito.any()); // 目標方法在測試中是否只執(zhí)行了1次
- 結(jié)合使用示例進行說明
// 基礎(chǔ)類結(jié)構(gòu)
class ServiceA {
public void a(int i) {
if (i == 1) {
this.b()
}
// do something
}
public void b() {
// do something
}
}
// 測試代碼的編寫
@Test
void testA() {
ServiceA service = Mockito.mock(ServiceA.class);
service.a(1);
Mockito.verify(service, Mockito.times(1)).b(); // 當a方法的傳入值為1時灸异,是否執(zhí)行了一次b方法
service.a(2);
Mockito.verify(service, Mockito.never()).b(); // 當a方法的傳入值為2時,是否從不執(zhí)行b方法
}
7. Mockito.mockStatic() 的使用(作用:模擬靜態(tài)方法調(diào)用)
類似于
Mockito.mock() + Mock.when()
或Mockito.mock() + Mock.verify()
的用法
Mockito.mockStatic()
在使用后必須調(diào)用close()
方法關(guān)閉,因此最好以try(xx) {}
的方式使用;
Mockito.mockStatic()
使用when()
須在后面跟thenXXX()
;
目標靜態(tài)方法無返回值
時, when()
后不能用thenReturn()
绎狭,但可以用thenThrow()
;
Mockito.mockStatic()
是范圍工作的 细溅,因此,調(diào)用此靜態(tài)方法的目標代碼必須在其生效范圍內(nèi)編寫;
- 示例1 - 有返回值的靜態(tài)方法
try (MockedStatic<NettyChannelMap> mockStatic = Mockito.mockStatic(NettyChannelMap.class)) {
mockStatic.when(() -> NettyChannelMap.getEquipmentChannel(Mockito.anyString())).thenReturn(new NioSocketChannel());
southboundDataService.handleCommandRequest1(equipmentCode, reboot); // 要測試的目標方法
}
- 示例2 - 無返回值的靜態(tài)方法
try (MockedStatic<ProtocolDataHandler> mockStatic = Mockito.mockStatic(ProtocolDataHandler.class);) {
commandManager.control(cc);
mockStatic.verify(() -> ProtocolDataHandler.sendDown(Mockito.any(), Mockito.anyInt()), Mockito.times(1));
}
8. 模擬內(nèi)部類的對象 與 類內(nèi)部的屬性的使用
public class A {
@Getter
public B b = new B();
public void a() {
this.getB().b();
}
public static class B {
public void b() {}
}
}
在如上的代碼中儡嘶,想要模擬b.b()
的調(diào)用需要使用如下的測試代碼
@Spy / @Mock
private A a;
@Test
public void testA() {
A.B b = Mockito.mock(A.B.class);
Mockito.when(a.getB()).thenReturn(b);
Mockito.doNothing().when(b).b();
a.a();
Mockito.verify(b, Mockito.time(1)).b();
}
9. Mockito 模擬與忽略 Thread.sleep() 或 TimeUtil.XXX.sleep()
默認情況下喇聊,Mockito是不允許
模擬Thread的,因此需要增設(shè)一個自己的類來包裹Thread.sleep()
操作
public class Sleeper {
public static void sleep(TimeUnit timeUnit, long val) throws InterruptedException {
timeUnit.sleep(val);
}
}
在業(yè)務(wù)代碼
需要使用 sleep 的地方蹦狂,替換為使用Sleeper.sleep()
來完成目的誓篱。
再測試時,使用Mockito.mockStatic(Sleeper.class)
的操作來代替對Thread
或者TimeUtil
的模擬凯楔。
try (MockedStatic<Sleeper> mockStatic = Mockito.mockStatic(Sleeper.class)) {
// ... 測試邏輯
mockStatic.verify(() -> Sleeper.sleep(Mockito.any(), Mockito.anyLong()), Mockito.never());
}
10. 不要模擬無限循環(huán)
對于無限循環(huán)窜骄,應(yīng)該將循環(huán)內(nèi)部的代碼放入單獨的方法中,測試時摆屯,使用Mockito對這個單獨出來的方法進行測試邻遏。