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}
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...