Mockito提升單元測試覆蓋率

hotMemo發表於2024-09-22
簡單實戰
1、準備工作
   從gitee下載一個例子工程,下面會在這個工程下補充測試用例

選擇的是 Ruoyi的後臺程式碼,gitclone之後,新增幾個依賴,因為習慣用

@RunWith(MockitoJUnitRunner.class) 的方式,就新增了下面的兩個依賴

第一個Mockito是這篇文章的主題,第二個junit4能讓我使用

@RunWith(MockitoJUnitRunner.class)的方式
也能使用Springboot自帶的test,但對我來說有億點點細節,偏離主題。

2、編寫基本架子

@InjectMock標註測試哪個類,比如這裡是測試 TokenController類,因為TokenController中的方法要用到StringUtils的靜態方法,在每個測試方法之前mock下靜態的工具類,需要在每次測試之後close一下,不然接下來的@Test方法會報錯。我講究是能一下子透過一個Controller方法就把之後的程式碼行給覆蓋到(當然大佬聽到這話至少有三句話要說...),所以SysLoginService的類我使用@Spy,@Spy能執行被註解類的方法裡。TokenService是二方包裡的,我用@Mock註解。

3、業務程式碼的邏輯

以TokenController 的logout方法為例

接收的引數是HttpServletRequest,這個直接Mock,無需new,不然要實現許多沒用的方法;首先是靜態類 SecurityUtils.getToken,第二行是StringUtils.isNotEmpy,要讓它走進裡面,這行必須為true,或者SecurityUtils.getToken設定return一個值;之後是jwtUtils.getUserName(token),這行必須返回個userName,供下面的sysLoginService.logout使用,然後是AuthUtil.logoutByToken,沒有返回值,直接doNothing()就行,接下來就是sysLoginService.logout,SysLoginService我用的@Spy,目的就是能從Conroller類覆蓋到Service類,還需要看下它裡面的邏輯,之後是R.ok()無需贅言。

SysLoginService 裡的logout只有一行邏輯,使用的類RecordLogService,它也在當前專案中,我也要讓它的recordLogininfor方法覆蓋到。如果在執行單元測試的時候能執行到RecordLogService呢,這個類是跟SysLoginService關聯的,在這個測試方法中,沒有直接跟TokenController直接關聯,所以在@Spy RecordLogService後,還得使用,如下:

ReflectUtils.setFieldValue(sysLoginService, "recordLogService", sysRecordLogService); 透過反射將 sysRecordLogService注入到sysLoginService中

看下 sysRecordLogService.recordLogininfor的邏輯

沒有什麼特殊,remoteLogService是二方包裡的方法,只需要將它的Mock物件透過ReflectionUtils set到sysRecordLogService中。

4、單元測試程式碼

完整程式碼:

@RunWith(MockitoJUnitRunner.class)
public class TokenControllerTest {
  // MockedStatic 是業務邏輯中要用的的工具類,需要mocked一下
MockedStatic<SecurityUtils> mockedStaticSecurityUtils;
MockedStatic<JwtUtils> mockedStaticJwtUtils;
MockedStatic<AuthUtil> mockedStaticAuthUtil;
  // 這個是上面AuthUtil要依賴的,static new 了一下,new的過程中用到了SpringUtils.getBean的方法,這個也需要mock,並且mock getBean這個方法
MockedStatic<SpringUtils> mockedStaticSpringUtils;
@Spy
private SysRecordLogService sysRecordLogService;
@Spy
private SysLoginService sysLoginService;
@Mock
private RemoteLogService remoteLogService;
@InjectMocks
private TokenController tokenController;
@Before // 每個用@Test標註的方法之前執行
public void setup() {
mockedStaticJwtUtils = Mockito.mockStatic(JwtUtils.class);
mockedStaticSecurityUtils = Mockito.mockStatic(SecurityUtils.class);
mockedStaticSpringUtils = Mockito.mockStatic(SpringUtils.class);
mockedStaticSpringUtils.when(() -> SpringUtils.getBean(eq(Class.class))).thenReturn(null);
mockedStaticAuthUtil = Mockito.mockStatic(AuthUtil.class);
ReflectUtils.setFieldValue(sysLoginService, "recordLogService", sysRecordLogService);
ReflectUtils.setFieldValue(sysRecordLogService, "remoteLogService", remoteLogService);
}
@After // 每個用@Test標註的方法之後執行
public void after() {
     // 每次@Test的方法執行後都必須close一下,不然之後的@Test方法報錯
mockedStaticJwtUtils.close();
mockedStaticSecurityUtils.close();
mockedStaticAuthUtil.close();
mockedStaticSpringUtils.close();
}
@Test
public void should_logout_ok_when_username_and_password_ok() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
mockedStaticSecurityUtils.when(() -> SecurityUtils.getToken(request)).thenReturn("111");
mockedStaticJwtUtils.when(() -> JwtUtils.getUserName("111")).thenReturn("111");
tokenController.logout(request);
verify(remoteLogService).saveLogininfor(Mockito.any(), eq(SecurityConstants.INNER));
}
}

5、SHOWTIME

相關文章