当前位置:网站首页>powermock实战
powermock实战
2022-07-21 02:28:00 【伊布拉西莫】
版本依赖
注意:powermockito与 mockito版本容易出现不一致的情况。
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<!-- 7.5.0 不兼容 -->
<version>7.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<!-- <artifactId>mockito-inline</artifactId> -->
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>2.0.9</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
</exclusion>
</exclusions>
</dependency>
业务背景
用户登录模块, 代码按照如下分层。 支持多种模式登录。
- controller
- service
- abstract 抽象实现
- MobileService,EmailService,AccountService 分别处理三种不同登录方式
- mapper
- po
- 其他
- Utils : 静态方法工具类
- PasswordEncoder : 普通加密方法类
代码结构
业务代码
Utils
public interface Utils {
static boolean checkIsEmail(String email) {
if ((email != null) && (!email.isEmpty())) {
return Pattern.matches("^(\\w+([-.][A-Za-z0-9]+)*){3,18}@\\w+([-.][A-Za-z0-9]+)*\\.\\w+([-.][A-Za-z0-9]+)*$", email);
}
return false;
}
static void checkIsMobile(String mobile) {
if ((mobile != null) && (!mobile.isEmpty())) {
boolean match = Pattern.matches("^((13[0-9])|(14[0|5|6|7|9])|(15[0-3])|(15[5-9])|(16[6|7])|(17[2|3|5|6|7|8])|(18[0-9])|(19[1|8|9]))\\d{8}$", mobile);
if(match){
return;
}
}
throw new RuntimeException("is not mobile");
}
}
PasswordEncoder
public class PasswordEncoder {
private static final Logger logger = LoggerFactory.getLogger(PasswordEncoder.class);
public String encryptPassword(String plainText) {
String encryptedText = plainText;
logger.debug("{} encrypted result: {}", plainText, encryptedText);
//todo encode 明文
return encryptedText;
}
}
UserMapper
public interface UserMapper {
User get(String account);
User getByMobile(String mobile);
User getByEmail(String email);
}
XService
接口
public interface ILoginService {
boolean support(String input);
Long login(String input, String password);
}
controller: 修改成员属性、父类属性mock
业务代码
public class UserController {
//mocklist
private List<ILoginService> loginServiceList;
public UserController(List<ILoginService> loginServiceList ){
this.loginServiceList = loginServiceList;
}
public Long login(String input , String password){
ILoginService loginService = loginServiceList.stream().filter(service -> service.support(input)).findFirst().orElseThrow(() -> new RuntimeException("未识别的账号类型"));
return loginService.login(input, password);
}
}
测试
public class UserControllerTest {
@InjectMocks
@Spy
private UserController userController;
@Mock
private List<ILoginService> loginServiceList;
@BeforeClass
public void setUp() throws IllegalAccessException {
MockitoAnnotations.openMocks(this);
}
String input = "[email protected]";
String password = "123456";
@Test
public void testLogin_throw() {
try {
userController.login(input, password);
Assert.fail("expect RuntimeException");
} catch (Exception e) {
Assert.assertTrue(e instanceof RuntimeException);
}
}
@Test
public void testLogin_pass() throws IllegalAccessException {
ILoginService loginService = mock(ILoginService.class);
when(loginService.support(anyString())).thenReturn(true);
when(loginService.login(anyString(), anyString())).thenReturn(99L);
List<ILoginService> list2 = Lists.newArrayList(loginService);
//重新设置mock对象成员变量属性
MemberModifier.field(UserController.class, "loginServiceList").set(userController, list2);
Long userId = userController.login(input, password);
Assert.assertTrue(userId.equals(99L));
}
}
AbstractService: 抽象类mock
业务代码
public abstract class AbstractLoginService implements ILoginService {
private static final Logger logger = LoggerFactory.getLogger(AbstractLoginService.class);
@Resource
private PasswordEncoder passwordEncoder;
protected final ThreadLocal<User> threadLocal = new ThreadLocal<>();
@Override
public Long login(String input, String password) {
logger.info("start to login {}/{}",input,password);
getUserInternal(input);
try{
User user = threadLocal.get();
String encodedPassword = passwordEncoder.encryptPassword(password);
if (user.getPassword().equals(encodedPassword)) {
return user.getId();
}
throw new RuntimeException("用户名密码错误");
}finally {
threadLocal.remove();
}
}
public abstract void getUserInternal(String input);
}
测试
public class AbstractLoginServiceTest {
@InjectMocks
// @Spy -- 无法使用spy ,打桩 抽象类
private AbstractLoginService abstractLoginService;
@Mock
private PasswordEncoder passwordEncoder;
@Mock
protected ThreadLocal<User> threadLocal;
@BeforeClass
public void setUp(){
//mock 抽象类
abstractLoginService = Mockito.mock(AbstractLoginService.class, Mockito.CALLS_REAL_METHODS);
MockitoAnnotations.openMocks(this);
}
@Test
public void testLogin() throws IllegalAccessException {
// mock(AbstractLoginService.class), 故无法使用真实的成员变量threadLocal, 也无法inject,
// 这里必须手动inject
MemberModifier.field(AbstractLoginService.class, "threadLocal").set(abstractLoginService, threadLocal);
//mock void 方法 ,do-nothing
// doNothing().when(threadLocal).remove();
User user = mock(User.class);
when(threadLocal.get()).thenReturn(user);
when(user.getPassword()).thenReturn("abc");
when(passwordEncoder.encryptPassword(anyString())).thenReturn("abc");
abstractLoginService.login("123", "xxx");
Assert.assertTrue(true);
try{
when(passwordEncoder.encryptPassword(anyString())).thenCallRealMethod();
abstractLoginService.login("123", "xxx");
Assert.fail("expect 用户名密码错误");
}catch (Exception e){
Assert.assertTrue(e instanceof RuntimeException);
Assert.assertTrue(e.getMessage().equals("用户名密码错误"));
}
}
}
MailLoginService: 静态方法+普通void()方法mock
业务类
public class MailLoginService extends AbstractLoginService {
private static final Logger logger = LoggerFactory.getLogger(MailLoginService.class);
@Resource
private UserMapper userMapper;
@Override
public boolean support(String email) {
return Utils.checkIsEmail(email);
}
@Override
public void getUserInternal(String email) {
User user = userMapper.getByEmail(email);
threadLocal.set(user);
}
}
测试方法
//@MockPolicy(Slf4jMockPolicy.class)
@PowerMockIgnore({
"javax.management.*", "jdk.internal.reflect.*"})
@PrepareForTest(Utils.class)
public class MailLoginServiceTest extends PowerMockTestCase {
@InjectMocks
@Spy
private MailLoginService mailLoginService;
@Mock
private UserMapper userMapper;
@BeforeClass
public void setUp(){
MockitoAnnotations.openMocks(this);
//static mock
PowerMockito.mockStatic(Utils.class);
}
@Test
public void testSupport() {
//static
PowerMockito.when(Utils.checkIsEmail(anyString())).thenReturn(true);
Assert.assertTrue(mailLoginService.support("email"));
PowerMockito.when(Utils.checkIsEmail(anyString())).thenReturn(false);
Assert.assertFalse(mailLoginService.support("email"));
}
@Test
public void testGetUserInternal() throws IllegalAccessException {
User user = mock(User.class);
when(userMapper.getByEmail(anyString())).thenReturn(user);
mailLoginService.getUserInternal("abc");
//修改父类属性 -- 为null
MemberModifier.field(MailLoginService.class, "threadLocal").set(mailLoginService, null);
try{
mailLoginService.getUserInternal("abc");
Assert.fail("expect NPE fail");
}catch (Exception e) {
Assert.assertTrue( e instanceof NullPointerException);
}
}
}
MobileLoginService: static void 方法 mock
业务方法
public class MobileLoginService extends AbstractLoginService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private UserMapper userMapper;
@Override
public boolean support(String mobile) {
try{
Utils.checkIsMobile(mobile);
return true;
}catch (Exception e) {
return false;
}
}
@Override
public void getUserInternal(String mobile) {
User user = userMapper.getByMobile(mobile);
threadLocal.set(user);
}
}
测试方法
@PowerMockIgnore({
"javax.management.*", "jdk.internal.reflect.*"})
@PrepareForTest(Utils.class)
public class MobileLoginServiceTest extends PowerMockTestCase {
@InjectMocks
@Spy
private MobileLoginService mobileLoginService;
@Mock
private UserMapper userMapper;
@BeforeClass
public void setUp(){
MockitoAnnotations.openMocks(this);
PowerMockito.mockStatic(Utils.class);
}
@Test
public void testSupport() throws Exception {
// mock static方法返回值为void
PowerMockito.doNothing().when(Utils.class,"checkIsMobile",any(String.class));
Utils.checkIsMobile("abc");
Assert.assertTrue(true);
PowerMockito.doThrow(new UnsupportedOperationException("不支持的操作")).when(Utils.class,"checkIsMobile",any(String.class));
try{
Utils.checkIsMobile("abc");
Assert.fail("expect 不支持的操作");
}catch (Exception e){
Assert.assertTrue(e instanceof UnsupportedOperationException);
}
}
}
边栏推荐
- Orepa: Ali proposed a heavy parameter strategy with fast training. The memory is halved and the speed is doubled | CVPR 2022
- canal. Deployer-1.1.6 encountered dump address has an error, retrying Caused by problem
- 视频融合云平台EasyCVR重启服务器后,级联离线如何解决?
- 数字孪生应用案例及常用技术
- MySQL的MVCC详细理解(2022版)
- 风控数据(模型)不扎心,只因好这道流水工序上的处理技巧
- Face recognition attendance system based on jsp/servlet
- ORACLE导出表数据-dmp文件
- DOS命令导出文件夹内所有文件的名称和全路径
- Technology Encyclopedia | cloud native
猜你喜欢
[translation] technical writing of developers
Php-cgi Remote Code Execution Vulnerability (cve-2012-1823)
Zero basic learning canoe panel (1) -- new panel
canal.deployer-1.1.6 遇到dump address has an error, retrying. caused by问题
HCIP第四天
手工遍历二叉树的技巧
Custom paging label
Face recognition attendance system based on jsp/servlet
Skiasharp's WPF self drawn bouncing ball (case version)
Involution: Inverting the Inherence of Convolution for Visual Recognition(CVPR2021)
随机推荐
How to solve the problem of cascading offline after the server is restarted on the video fusion cloud platform easycvr?
canal. Deployer-1.1.6 encountered dump address has an error, retrying Caused by problem
Zero basic learning canoe panel (1) -- new panel
风控数据(模型)不扎心,只因好这道流水工序上的处理技巧
C语言练习题目+答案:21-25
Digital twin technology offshore wind farm solution
下一代无线局域网--802.11n与传统11ag OFDM设备间的PHY互操作性
生成二维码
Control in canoe panel: switch/indicator
resttemplate调用post\get
SOHO @ L2TP/IPsec Issue
零基础学习CANoe Panel(1)—— 新建 Panel
众昂矿业:萤石行业发展四大趋势
STM32 DHT11温湿度传感器模块学习总结
Skiasharp's WPF self drawn bouncing ball (case version)
安防视频监控平台EasyCVR数据库字段无法更新,如何优化?
Pagination universelle (encapsulation du Code de pagination)
Linux中安装Redis
Custom paging label
[translation] principles for designing and deploying extensible applications on kubernetes