工具
  我将在下面介绍下PowerMockRunner和SpringRunner两个单元测试的运行环境。

SpringRunner

  SpringRunner 继承了SpringJUnit4ClassRunner,SpringRunner是SpringJUnit4ClassRunner的一个别名,没有扩展任何功能。下面我们来看下示例

  1. package com.hly.unitest.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.hly.unitest.entity.UserInfo;
  4. import com.hly.unitest.service.UserService;
  5. import org.junit.Test;
  6. import org.junit.runner.RunWith;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.context.SpringBootTest;
  9. import org.springframework.test.context.junit4.SpringRunner;
  10. @RunWith(SpringRunner.class)
  11. @SpringBootTest
  12. public class UserServiceNoMockTest {
  13. @Autowired
  14. private UserService userService;
  15. @Test
  16. public void getUser() throws Exception {
  17. UserInfo result = userService.getUser("6f49aa9e-1afc-4439-ad21-25ae90dde566");
  18. System.out.println("the 1st time: result = " + JSON.toJSONString(result));
  19. }
  20. }

分析:
首先我们分析下注解:
@RunWith: 用于指定junit运行环境,是junit提供给其他框架测试环境接口扩展,为了便于使用spring的依赖注入,spring提供了org.springframework.test.context.junit4.SpringJUnit4ClassRunner作为JUnit测试环境。在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。我们此篇文章只探讨SpringRunner和PowerMockRunner。

@SpringBootTest: 替代了spring-test中的@ContextConfiguration注解,目的是加载ApplicationContext,启动spring容器, @SpringBootTest注解会自动检索程序的配置文件,检索顺序是从当前包开始,逐级向上查找被@SpringBootApplication或@SpringBootConfiguration注解的类。

提出问题: 这样的测试方式会连接数据库,进行网络请求等等耗时操作,甚至数据库中的准备数据是个非常麻烦的事情,各个逻辑分支如果都需要测试,这在大型的项目中工作量是不可想象的,所以有没有可能对数据库这种操作直接不处理,这需要mock能力,为此引入了MockBean,下面来看示例:
[

](https://blog.csdn.net/rajayu/article/details/103567036)

  1. package com.hly.unitest.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.hly.unitest.dao.UserInfoMapper;
  4. import com.hly.unitest.entity.UserInfo;
  5. import com.hly.unitest.service.UserService;
  6. import org.junit.Test;
  7. import org.junit.runner.RunWith;
  8. import org.mockito.Mockito;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.boot.test.context.SpringBootTest;
  11. import org.springframework.boot.test.mock.mockito.MockBean;
  12. import org.springframework.test.context.junit4.SpringRunner;
  13. import static org.mockito.ArgumentMatchers.any;
  14. @RunWith(SpringRunner.class)
  15. @SpringBootTest
  16. public class UserServiceTest {
  17. @Autowired
  18. private UserService userService;
  19. @MockBean
  20. private UserInfoMapper userInfoMapper;
  21. @Test
  22. public void getUser() {
  23. UserInfo result = userService.getUser("6f49aa9e-1afc-4439-ad21-25ae90dde566");
  24. System.out.println("the 1st time: result = " + JSON.toJSONString(result));
  25. UserInfo userInfo = mockUserInfo();
  26. Mockito.when(userInfoMapper.selectByUserId(any())).thenReturn(userInfo);
  27. result = userService.getUser("6f49aa9e-1afc-4439-ad21-25ae90dde566");
  28. System.out.println("the 2nd time: result = " + JSON.toJSONString(result));
  29. }
  30. private UserInfo mockUserInfo() {
  31. UserInfo userInfo = new UserInfo();
  32. userInfo.setUserId("654321");
  33. userInfo.setUserName("B");
  34. userInfo.setPassword("pwB");
  35. userInfo.setGender("女");
  36. return userInfo;
  37. }
  38. }

分析:
  我们在UserInfoMapper上加了注解@MockBean,这个注解的意思所有的UserInfoMapper的方法都被mock,返回都将变成null,所以第一次返回值为null;我们在测试方法中借助Mockito.when将userInfoMapper.selectByUserId的返回值进行自定义,这样就完成了对数据库的返回对象的控制,不用怕数据库的更改导致测试用例的分支没有跑完成的问题出现。

提出问题: 我们发现这样的处理虽然符合了上个问题的预期,但是又有了新的问题出现,第一:既然将数据库和网络操作全部mock,那好像就没有必要启动Spring,@MockBean有个特别大的危害就是导致频繁的启动Spring,而spring boot 的启动时间比较耗时,所以@MockBean,很有可能导致测试运行的很慢;第二:如果有这样一个方法,是UserService 的私有方法,里面特别复杂的操作,我并不想在这个测试用例中进行测试,可以不可以mock,或者有一个静态类,类中的方法有没有方式mock,还有UserService的变量我又没有方式可以直接赋值等等,这些SpringRunner也就是SpringJUnit4ClassRunner都是没有办法解决的,这需要下面的有个JUNIT工具解决,也就是PowerMockRunner。
[

](https://blog.csdn.net/rajayu/article/details/103567036)
PowerMockRunner
  PowerMock基本上cover了所有Mockito不能支持的case(大多数情况也就是静态方法,但其实也可以支持私有方法和构造函数的调用)。PowerMock使用了字节码操作,因此它是自带Junit runner的。在使用PowerMock时,必须使用@PrepareForTest注释被测类,mock才会被执行。下面我们来看示例:
对UserService进行单元测试:

  1. package com.hly.unitest.service;
  2. import com.hly.unitest.dao.UserInfoMapper;
  3. import com.hly.unitest.entity.UserInfo;
  4. import com.hly.unitest.model.UserInfoRequest;
  5. import com.hly.unitest.util.CommonUtil;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. import java.util.Date;
  9. @Service
  10. public class UserService {
  11. private String finalStr = "finalStr";
  12. @Autowired
  13. private UserInfoMapper userInfoMapper;
  14. public void save(UserInfoRequest request) {
  15. UserInfo userInfo = new UserInfo();
  16. userInfo.setUserId(request.getUserId());
  17. userInfo.setUserName(request.getUserName());
  18. userInfo.setPassword(request.getPassword());
  19. userInfo.setGender(request.getGender());
  20. Date currDate = CommonUtil.getMaxDayOfThisMonth(new Date());
  21. userInfo.setCreateTime(currDate);
  22. userInfo.setUpdateTime(currDate);
  23. userInfoMapper.insertSelective(userInfo);
  24. }
  25. public UserInfo getUserInfo(String userId) {
  26. if (CommonUtil.isEmpty(userId)) {
  27. return null;
  28. }
  29. String assetStr = getAssetStr0("a", "b");
  30. if (!"un".equals(assetStr)){
  31. System.out.println("assetStr = " + assetStr);
  32. return null;
  33. }
  34. getAssetNull();
  35. UserInfo userInfo = userInfoMapper.selectByUserId(userId);
  36. return userInfo;
  37. }
  38. public UserInfo getUser(String userId) {
  39. UserInfo userInfo = userInfoMapper.selectByUserId(userId);
  40. return userInfo;
  41. }
  42. public String testStaticMethod() {
  43. String randomStr = CommonUtil.generateUUID();
  44. return randomStr;
  45. }
  46. public String testClassVariables() {
  47. return finalStr;
  48. }
  49. private String getAssetStr() {
  50. System.out.println("getAssetStr");
  51. return "unit-test";
  52. }
  53. private String getAssetStr0(String a, String b) {
  54. System.out.println("getAssetStr0");
  55. return "unit-test";
  56. }
  57. private void getAssetNull() {
  58. System.out.println("getAssetNull");
  59. return;
  60. }
  61. }
  1. package com.hly.unitest.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.hly.unitest.dao.UserInfoMapper;
  4. import com.hly.unitest.entity.UserInfo;
  5. import com.hly.unitest.service.UserService;
  6. import com.hly.unitest.util.CommonUtil;
  7. import org.junit.Before;
  8. import org.junit.Test;
  9. import org.junit.runner.RunWith;
  10. import org.mockito.InjectMocks;
  11. import org.mockito.Mock;
  12. import org.powermock.api.mockito.PowerMockito;
  13. import org.powermock.core.classloader.annotations.PrepareForTest;
  14. import org.powermock.modules.junit4.PowerMockRunner;
  15. import org.powermock.reflect.Whitebox;
  16. import org.springframework.test.util.ReflectionTestUtils;
  17. import static org.mockito.ArgumentMatchers.any;
  18. import static org.mockito.Mockito.when;
  19. @RunWith(PowerMockRunner.class)
  20. @PrepareForTest({UserService.class, CommonUtil.class})
  21. public class UserServicePowerRunnerTest {
  22. @InjectMocks
  23. private UserService userService;
  24. @Mock
  25. private UserInfoMapper userInfoMapper;
  26. @Before
  27. public void setUp() {
  28. // mock私有方法
  29. userService = PowerMockito.spy(new UserService());
  30. ReflectionTestUtils.setField(userService, "userInfoMapper", userInfoMapper);
  31. }
  32. @Test
  33. public void getUserInfo() throws Exception {
  34. UserInfo userInfo = mockUserInfo();
  35. when(userInfoMapper.selectByUserId(any())).thenReturn(userInfo);
  36. // 控制私有方法的返回值
  37. PowerMockito.doReturn("un").when(userService, "getAssetStr0", any(), any());
  38. UserInfo result = userService.getUserInfo("6f49aa9e-1afc-4439-ad21-25ae90dde566");
  39. System.out.println(JSON.toJSONString(result));
  40. }
  41. @Test
  42. public void testStaticMethod() throws Exception {
  43. String result = userService.testStaticMethod();
  44. System.out.println("the 1st time: result = " + result);
  45. // mock静态方法
  46. PowerMockito.mockStatic(CommonUtil.class);
  47. String str = "123";
  48. when(CommonUtil.generateUUID()).thenReturn(str);
  49. result = userService.testStaticMethod();
  50. System.out.println("the 2nd time: result = " + result);
  51. }
  52. @Test
  53. public void testClassVariables() throws Exception {
  54. String result = userService.testClassVariables();
  55. System.out.println("the 1st time: result = " + result);
  56. // mock类变量
  57. Whitebox.setInternalState(userService, "finalStr", "FINAL-STR");
  58. result = userService.testClassVariables();
  59. System.out.println("the 2nd time: result = " + result);
  60. }
  61. private UserInfo mockUserInfo() {
  62. UserInfo userInfo = new UserInfo();
  63. userInfo.setUserId("654321");
  64. userInfo.setUserName("B");
  65. userInfo.setPassword("pwB");
  66. userInfo.setGender("女");
  67. return userInfo;
  68. }
  69. }

https://blog.csdn.net/rajayu/article/details/103567036
https://www.cnblogs.com/AdaiCoffee/p/10700097.html