java单元测试:powermock + SpringBootTest

Unit Test

Unit testing private constructors and getter setters
How can I do unit test for hashCode()?
Unit tests for constructors
How to unit test constructors

https://javarevisited.blogspot.com/2018/09/junit-testing-tips-constructor-is-called-before-test-methods.html
https://hackernoon.com/unit-test-naming-given-when-then-java-84407911b858
https://davesquared.net/2011/04/dont-mock-types-you-dont-own.html
https://stackoverflow.com/questions/16043819/junit-testing-void-methods
https://codingcraftsman.wordpress.com/2015/02/20/java-mockito-and-mocking-a-callback/
http://derekwilson.net/blog/2016/05/29/unit-testing-android-async-callback-functions
https://semaphoreci.com/community/tutorials/stubbing-and-mocking-with-mockito-2-and-junit

Powermock

问题:maven pom.xml配置
    
        1.8
        2.0.0-beta.5
    

添加依赖包的时候,把powermock和相关依赖包放在junit4及其相关依赖包的上面或者前面。

        
            org.powermock
            powermock-module-junit4
            ${powermock.version}
            test
        
        
            org.powermock
            powermock-api-mockito2
            ${powermock.version}
            test
        
        
            junit
            junit
            4.12
            test
        
问题:org.mockito.exceptions.misusing.NullInsteadOfMockException

org.mockito.exceptions.misusing.NullInsteadOfMockException: Argument passed to when() is null! Example of correct stubbing: doThrow(new RuntimeException()).when(mock).someMethod(); Also, if you use @Mock annotation don't miss initMocks()

解决:
在测试类中添加如下代码

    @Before
     public void setUp() {
            MockitoAnnotations.initMocks(this);
    }

或者在测试类的头部添加如下行

@RunWith(PowerMockRunner.class)
问题:被mock的Receipt的setContainer函数不生效
@Component
public class CallbackManager {
    public void update(Receipt receipt, String containerCode) {
        receipt.setContainer(containerCode);
    }
}

解决:
估计原因是因为Receipt是从参数传过来的,修改参数为mock对象

@RunWith(PowerMockRunner.class)
public class CallbackManagerTest {

    @Autowired
    @InjectMocks
    CallbackManager callbackManager;

    @Mock
    Receipt mockReceipt;
    
    @Test
    public void update_receipt_successTest() {
        PowerMockito.doNothing().when(mockReceipt).setContainer(ArgumentMatchers.anyObject());
        //below line will not work
//      callbackManager.update_receipt_success(generateReceipt(), "2");
        callbackManager.update_receipt_success(mockReceipt, "2");
        
        verify(mockReceipt, times(1)).setContainer(ArgumentMatchers.anyObject());
    }
}
问题:2 matchers expected, 1 recorded

待MOCK的函数是setStatus

#Receipt receipt = xxxx
receipt.setStatus(false, TaskStatusEnum.FAILED.name(), "旷视返回失败cmdN0:" + model.getCmdNo());

测试代码如下:

@Test
    public void aresProcessReturnTest() {   
#some other codes...
        PowerMockito.doNothing().when(mockReceipt).setStatus(false, ArgumentMatchers.anyObject(),  ArgumentMatchers.anyObject());
#some other codes...

}

原因:ArgumentMatchers和布尔类型还有字符串类型等等不能同时使用。
解决方法,修改为如下:

//使用正则表达式匹配
        PowerMockito.doNothing().when(mockReceipt).setStatus(eq(false), eq(TaskStatusEnum.FAILED.name()),  ArgumentMatchers.matches(".*?"));
问题:java.lang.NoSuchMethodException: com.xxx.xxx.xxx.AresBindingReturnApiModel$DataBean.get$jacocoData()

这个异常在eclipse里执行单元测试的时候不会出现,但是通过mvn调用jacoco时会出现,原因是jacoco在运行时会在代码里注入A private static field jacocoData and a private static method jacocoInit(),通过反射调用方法的时候就有可能出现这个异常。
解决办法:要么通过if else过滤掉,要么通过Field的isSynthetic()过滤掉。修改代码ignore synthetic members

问题:Maven Jacoco Configuration - Exclude classes/packages from report

可以通过excludes字段设置。一个星号匹配0个或者多个字符;两个连着的星号是匹配0个或者多个目录。一个问号匹配一个字符。下属设置排除model目录及其子目录中的报告,也即不统计他们的代码覆盖率。


        
                    
                org.jacoco
                jacoco-maven-plugin
                0.7.8
                
                    
                        com/jiuxia/model/**/*
                        **/*Config.*
                        **/*Dev.*
                    
                    ${project.build.directory}/coverage-reports/jacoco-ut.exec
                    ${project.build.directory}/coverage-reports/jacoco-ut.exec
                    ${skipTests}
                    file
                    true
                



问题:Ignoring Lombok Code in Jacoco

解决办法:
Jacoco 0.8.0+ 和 Lombok v1.16.14+提供了相应支持。在项目根目录的lombok.config文件里添加如下内容

config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
问题:Powermock+springbootTest

例子

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest
public class MyControllerTest {
    @Autowired
    @InjectMocks
    MyController mockMyController;
    
    
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;
    
    @Mock
    MyManager mockMyManager;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    
    @Test
    public void parserTest() throws Exception {
        
        RequestBuilder request = null;
        
        MyReturnApiModel requestBody = new MyReturnApiModel ();
        ReturnDataBean data = new ReturnDataBean();
        data.setActionID("action-1");
        data.setCmdNo(1);
        data.setCmdRes("cmdRes");
        data.setStatus(0);
        data.setWarehouseID("warehouse-1");
        requestBody.setData(data );
        String content = new ObjectMapper().writeValueAsString(requestBody);

        PowerMockito.doNothing().when(mockMyManager).processReturn( ArgumentMatchers.any());
        mockMvc.perform(post("/action/callback").contentType(MediaType.APPLICATION_JSON).content(content))
        .andExpect(status().isOk());
    }
}

其中:SpringRunner.class是SpringJUnit4ClassRunner.class的别名。
powermock和springbootTest同时使用的时候要用powermock代理springbootTest

问题:Powermock+springbootTest测试spring程序的速度很慢

原因:每个集成了powermock+spring环境的测试类,都需要重新启动spring的TestContext,耗费了大量时间。spring会重用上个TestContext,使用 @RunWith(PowerMockRunner.class) 注解的类,这种方式是不能重用TestContext的,PowerMock代理了大多数类,ClassLoader和标准运行的方式不同,所以不行。
解决:能不用启动spring的尽量不要启动spring;尽量避免powermock+spring一起用。

问题:java.lang.ClassCastException

假如待测试类中使用到了XML解析相关的包和类, 在运行的时候会报上述类似异常。
解决办法:在测试类最上方添加PowerMockIgnore,例如:

@PowerMockIgnore({"org.xml.*", "javax.xml.*"})
问题:ContextConfiguration

问题:获取或者修改springboot @value的值

获取:

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest
public class TaskProcessManagerTest3 {

    @Autowired
    @InjectMocks
    TaskProcessManager taskManager;

    @Value("${oa.url}")
    private String url;

//  @Before
//    public void setUp() {
//        MockitoAnnotations.initMocks(this);
//    }

    @Test
    public void SendCommitTaskToWMSTest() {
        System.out.println(url);
    }
}

修改

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = { "oa.url=http://www.abc.com", })
public class TaskProcessManagerTest3 {

    @Autowired
    @InjectMocks
    TaskProcessManager taskManager;

    @Value("${oa.url}")
    private String url;

//  @Before
//    public void setUp() {
//        MockitoAnnotations.initMocks(this);
//    }

    @Test
    public void SendCommitTaskToWMSTest() {
        System.out.println(url);
    }
}
问题:Cannot instantiate @InjectMocks field named 'mockMyClass'! Cause: the type 'MyClass' is an inner non static class

如果没有特别的原因,内部类一般建议设置为static。如果设置为static对内部类同时使用autowired和injectmock的时候就不会报这个错误啦。

被测试类(未修改内部类为static)

@Component
public class TaskProcessManager {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());

    @Component
    @Async
    public class SendTask {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());

        @Autowired
        private RestTemplate restTemplate;

        @Value("${oa.url}")
        private String url;

        public void sendCommitRequest(MyModel param) {
            System.out.println("async tasks started:" + Thread.currentThread().getName());
            restTemplate.getRequestFactory();
            System.out.println("url:" + url);
            StringBuffer urlSb = new StringBuffer(url);
            urlSb.append("/tasklist/task");
            logger.info("commit one task:url:{}", urlSb.toString());
            logger.info("commit task:content:{}", JSON.toJSONString(param));
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity httpEntity = new HttpEntity(param, headers);
            try {
                ResponseEntity exchange = restTemplate.postForEntity(urlSb.toString(), httpEntity, String.class);
                logger.info("commit task succeed:");
            } catch (Exception e) {
                logger.info("commit task failed:" + e.getMessage());
                throw new RuntimeException("commit task failed!");
            }
        }
    }
}

单元测试代码(假设上面的内部类已经修改为static)

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest
@PrepareForTest({SendTask.class})
public class TaskProcessManagerTest {
    
    @Autowired
    @InjectMocks
    SendTask mockSendTask;
    
    @Mock
    RestTemplate mockRestTemplate;

     @Value("${oa.url}")
     private String url;
     
//  @Before
//    public void setUp() {
//        MockitoAnnotations.initMocks(this);
//    }
    
    @Test
    public void sendCommitRequestTest() {
        //已把内部类改成static
        MyModel myModel = new MyModel();
        myModel.setTaskID(3);
        myModel.setWorkflowID("workflow-2");
        ResultBean result = new ResultBean();
        result.setSuccess(true);
        
        myModel.setResult(result );
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity httpEntity = new HttpEntity(taskApiModel, headers);
        
        ResponseEntity responseEntity = new ResponseEntity("lalala", HttpStatus.OK);
        
        when(mockRestTemplate.postForEntity(eq(url + "/tasklist/task"),eq(httpEntity), eq(String.class))).thenReturn((ResponseEntity) responseEntity);
        mockSendTask.sendCommitRequest(myModel);
        Mockito.verify(mockRestTemplate).postForEntity(eq(url + "/tasklist/task"),eq(httpEntity), eq(String.class));

    }
}

不把内部类设置为static肯定也可以做单元测试,TBD...

你可能感兴趣的:(java单元测试:powermock + SpringBootTest)