功能代码
这是一个后台管理框架中的登录功能
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result<JSONObject> login(@RequestBody SysLoginModel sysLoginModel){
Result<JSONObject> result = new Result<JSONObject>();
/**
* 验证码校验
*/
String username = sysLoginModel.getUsername();
String password = sysLoginModel.getPassword();
String captcha = sysLoginModel.getCaptcha();
if(captcha==null){
result.error500("验证码无效");
return result;
}
String lowerCaseCaptcha = captcha.toLowerCase();
String origin = lowerCaseCaptcha+sysLoginModel.getCheckKey()+jeecgBaseConfig.getSignatureSecret();
String realKey = Md5Util.md5Encode(origin, "utf-8");
Object checkCode = redisUtil.get(realKey);
if(checkCode==null || !checkCode.toString().equals(lowerCaseCaptcha)) {
log.warn("验证码错误,key= {} , Ui checkCode= {}, Redis checkCode = {}", sysLoginModel.getCheckKey(), lowerCaseCaptcha, checkCode);
result.error500("验证码错误");
result.setCode(HttpStatus.PRECONDITION_FAILED.value());
return result;
}
/**
* 用户校验
*/
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getUsername,username);
SysUser sysUser = sysUserService.getOne(queryWrapper);
result = sysUserService.checkUserIsEffective(sysUser);
if(!result.isSuccess()) {
return result;
}
String userpassword = PasswordUtil.encrypt(username, password, sysUser.getSalt());
String syspassword = sysUser.getPassword();
if (!syspassword.equals(userpassword)) {
addLoginFailOvertimes(username);
result.error500("用户名或密码错误");
return result;
}
/**
* 生成token
*/
userInfo(sysUser, result);
redisUtil.del(realKey);
//update-begin--Author:liusq Date:20210126 for:登录成功,删除redis中的验证码
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
LoginUser loginUser = new LoginUser();
BeanUtils.copyProperties(sysUser, loginUser);
baseCommonService.addLog("用户名: " + username + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
//update-end--Author:wangshuai Date:20200714 for:登录日志没有记录人员
return result;
}
代码分析
单元测试目标: 在单元测试中,我们关注的是被测函数实现了什么功能,接受了什么参数,返回了什么,而不是其具体实现。
用例分析:
- 测试所有参数正确的登录场景。
- 测试验证码错误或无效的场景。
- 测试用户名或密码错误的场景。
- 测试参数为空或null的情况。
单元测试编写: 在单元测试过程中,我们不应依赖外部环境(如数据库)。因此,应该通过Mock来模拟这些外部服务的调用。
Mock服务: 在上面的代码中,我们需要Mock的服务有 ISysUserService、RedisUtil、ISysDepartService等。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestLoginController {
@Mock
private RedisUtil redisUtil;
@Mock
private ISysUserService sysUserService;
@Mock
private JeecgBaseConfig jeecgBaseConfig;
@Mock
private ISysDepartService sysDepartService;
@Mock
ISysDictService sysDictService;
@Mock
private BaseCommonService baseCommonService;
@InjectMocks
private LoginController loginController;
@Test
public void testLoginValid() throws Exception {
SysLoginModel model = new SysLoginModel();
model.setUsername("testUser");
model.setPassword("testPassword");
model.setCaptcha("ABCD");
model.setCheckKey("testKey");
String salt = "salt1234";
SysUser sysUser = new SysUser();
sysUser.setUsername("testUser");
sysUser.setSalt(salt);
String encrypt = PasswordUtil.encrypt("testUser", "testPassword", salt);
sysUser.setPassword(encrypt);
when(redisUtil.get(any())).thenReturn("abcd");
when(sysUserService.getOne(any())).thenReturn(sysUser);
when(jeecgBaseConfig.getSignatureSecret()).thenReturn("secretKey");
Result<?> res = new Result<>();
when(sysUserService.checkUserIsEffective(any())).thenReturn(res);
Result<?> login = loginController.login(model);
assertTrue(login.isSuccess());
}
注解解释
@Mock
注解用于创建一个mock对象。当你在一个测试类中使用 @Mock 注解,Mockito会自动为你创建一个mock实例。这个mock对象可以用来模拟任何Java接口或类的行为,包括其方法的返回值、抛出的异常等。
@InjectMocks
@InjectMocks 注解则用于标记测试类中的一个实例变量,该变量将会被Mockito注入mock依赖。也就是说,你通常会用它来创建一个“主”对象,而这个对象依赖于其他通过 @Mock 创建的对象。Mockito会自动将所有被 @Mock 标记的对象注入到被 @InjectMocks 标记的对象中。
在这两个注解的帮助下我们可以轻松的替换login()需要的外部环境。
在这里,我们的传参为自定义的 SysLoginModel,并模拟了数据库的查询操作,将查询结果直接赋值为自定义的 SysUser。通过这种方式,我们可以判断在用户名、密码、验证码都正确的情况下,程序返回的结果。同时,我们可以继续编写其他测试用例来验证代码在各种异常情况下的表现。
结论
通过以上方法,我们能够有效地模拟外部环境,验证不同登录场景下的代码逻辑。通过Mock对象,我们可以独立于数据库等外部系统,测试登录功能的各个方面。进一步的测试可以通过编写更多的用例来覆盖所有可能的情况,例如:
- 验证码错误或无效的场景。
- 用户名或密码错误的场景。
- 参数为空或null的情况。
这种方法不仅提高了测试的可靠性,还确保了代码在各种情况下的稳健性和正确性。
6733

被折叠的 条评论
为什么被折叠?



