选择的是 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