• powermock实战


    版本依赖

    注意:powermockito与 mockito版本容易出现不一致的情况。

    <dependency>
          <groupId>org.testnggroupId>
          <artifactId>testngartifactId>
          
          <version>7.4.0version>
          <scope>testscope>
        dependency>
        
     <dependency>
          <groupId>org.mockitogroupId>
    
          <artifactId>mockito-coreartifactId>
          <version>3.12.4version>
          <scope>testscope>
        dependency>
    
        <dependency>
          <groupId>org.powermockgroupId>
          <artifactId>powermock-api-mockito2artifactId>
          <version>2.0.9version>
          <scope>testscope>
        dependency>
    
        <dependency>
          <groupId>org.powermockgroupId>
          <artifactId>powermock-module-testngartifactId>
          <version>2.0.9version>
          <scope>testscope>
          <exclusions>
            <exclusion>
              <groupId>org.testnggroupId>
              <artifactId>testngartifactId>
            exclusion>
          exclusions>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    业务背景

    用户登录模块, 代码按照如下分层。 支持多种模式登录。

    • 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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    UserMapper

    public interface UserMapper {
        User get(String  account);
        User getByMobile(String mobile);
        User getByEmail(String email);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    XService

    接口

    public interface ILoginService {
       boolean support(String input);
        Long login(String input, String password);
    }
    
    • 1
    • 2
    • 3
    • 4

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    测试

    public class UserControllerTest {
    
        @InjectMocks
        @Spy
        private UserController userController;
    
        @Mock
        private List<ILoginService> loginServiceList;
    
        @BeforeClass
        public void setUp() throws IllegalAccessException {
            MockitoAnnotations.openMocks(this);
        }
    
        String input = "a@b.com";
        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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    测试

    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("用户名密码错误"));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    测试方法

    //@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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    测试方法

    @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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
  • 相关阅读:
    抖音小程序开发教学系列(7)- 抖音小程序的发布与运营
    【PHP实现微信公众平台开发—基础篇】第2章 微信公众账号及申请流程详解
    【JAVA基础】面向对象基础
    java计算机毕业设计课题申报系统MyBatis+系统+LW文档+源码+调试部署
    【Matlab】二维绘图函数汇总
    触觉智能分享-SSD20X Ubuntu 20.04 文件系统的移植
    python中的map函数
    怒刷LeetCode的第6天(Java版)
    windows service 服务器安装 MySQL
    12、人工智能、机器学习、深度学习的关系
  • 原文地址:https://blog.csdn.net/it_freshman/article/details/125902564