Junit Mockito 的使用記錄

以下內(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對這個單獨出來的方法進行測試邻遏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市虐骑,隨后出現(xiàn)的幾起案子准验,更是在濱河造成了極大的恐慌,老刑警劉巖廷没,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糊饱,死亡現(xiàn)場離奇詭異,居然都是意外死亡颠黎,警方通過查閱死者的電腦和手機另锋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狭归,“玉大人夭坪,你說我怎么就攤上這事“ν” “怎么了台舱?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長潭流。 經(jīng)常有香客問我竞惋,道長,這世上最難降的妖魔是什么灰嫉? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任拆宛,我火速辦了婚禮,結(jié)果婚禮上讼撒,老公的妹妹穿的比我還像新娘浑厚。我一直安慰自己股耽,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布钳幅。 她就那樣靜靜地躺著物蝙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪敢艰。 梳的紋絲不亂的頭發(fā)上诬乞,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音钠导,去河邊找鬼震嫉。 笑死,一個胖子當著我的面吹牛牡属,可吹牛的內(nèi)容都是我干的票堵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼逮栅,長吁一口氣:“原來是場噩夢啊……” “哼悴势!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起措伐,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤瞳浦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后废士,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蝇完,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年官硝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片短蜕。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡氢架,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朋魔,到底是詐尸還是另有隱情岖研,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布警检,位于F島的核電站孙援,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扇雕。R本人自食惡果不足惜拓售,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镶奉。 院中可真熱鬧础淤,春花似錦崭放、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玻侥,卻和暖如春决摧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背使碾。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工蜜徽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人票摇。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓拘鞋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親矢门。 傳聞我的和親對象是個殘疾皇子盆色,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345