junit4 ranger java test
看ranger源码发现个小问题, 业务逻辑里只修改两行代码, 于是直接发起了jira issue, 同时在github发起pr. 结果修改的代码在一两个小时后竟然被另一个通过的pr给覆盖了, 亏大了. 本来还想写下修改部分的测试代码, 但是摸索了一下ranger的测试, 发现工作量太庞大了, 只得放弃. 这篇就是摸索 junit 测试代码 的小结.
对于springboot这种层层嵌套beans的框架, mockito的构建实在太麻烦了. 如何设计覆盖调用链路的一条测试路径mock, 非常耗时.
测试后的关键tips:
@InjectMocks并不是mock这个类本身, 而是使得这个类的元素支持mock注入, 接受@mock标记的类做为beans, 不然子变量并不会自动被mock处理.@mock会导致所有输出默认为null, 因此使用了@mock之后, 需要使用Mockito.when(xxx.call()).then(yyy)定义输入输出.- 由于
@mock会直接截断bean, 因此如果某个mock对象的父对象已经被mock了, 这个子对象的mock其实并不会被调用. - 除了使用
@mock, 还可以使用ABC abc = Mockito.mock(ABC.class), 然后执行Mockito.when(abc.call()).then(yyy)定义输入输出. - 对类使用
@Spy的话, 则不会所有函数都直接返回null, 而是执行真实的业务逻辑, 除非某个方法被设置了mock. 这个感觉更合理.
参考文档
关于java 单元测试Junit4和Mock的一些总结
https://www.cnblogs.com/wuyun-blog/p/7081548.html
@Mock 生成的类,所有方法都不是真实的方法,而且返回值都是NULL。
when(dao.getOrder()).thenReturn("returened by mock ");
@Spy 生成的类,所有方法都是真实方法,返回值都是和真实方法一样的。
Creates a spy of the real object. The spy calls real methods unless they are stubbed.
doReturn("twotwo").when(ps).getPriceTwo();
由于Spring可以使用@Autoware类似的注解方式,对私有的成员进行赋值,可以通过引入ReflectionTestUtils 解决依赖注入的问题。
ReflectionTestUtils.setField(AopTargetUtils.getTarget(appInfoService), "openAppInfoMapper",openAppInfoMapperMock);
@Test
public void test() {
//test for Mock
List mockList = Mockito.mock(List.class);
mockList.add("1");
System.out.println(mockList.get(0));//返回null,说明并没有调用真正的方法
Mockito.when(mockList.size()).thenReturn(100);//stub
System.out.println(mockList.size());//size() method was stubbed,返回100
//test for Spy
List list = new LinkedList();
List spy = Mockito.spy(list);
//optionally, you can stub out some methods:
Mockito.when(spy.size()).thenReturn(100);
//using the spy calls real methods
spy.add("one");
spy.add("two");
//prints "one" - the first element of a list
System.out.println(spy.get(0));
//size() method was stubbed - 100 is printed
System.out.println(spy.size());
}
Difference between @Mock and @InjectMocks
@InjectMocks是用来支持@Mock变量注入的, 用在主类上.
这个问答说的就很详细了, 毕竟官方文档看不下去.
https://stackoverflow.com/questions/16467685/difference-between-mock-and-injectmocks
@Mock creates a mock. @InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock (or @Spy) annotations into this instance.
Note you must use @RunWith(MockitoJUnitRunner.class) or Mockito.initMocks(this) to initialize these mocks and inject them (JUnit 4).
With JUnit 5, you must use @ExtendWith(MockitoExtension.class).
@RunWith(MockitoJUnitRunner.class) // JUnit 4
// @ExtendWith(MockitoExtension.class) for JUnit 5
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
// tests...
}
https://stackoverflow.com/a/33257512/6333140
如果主类不使用@RunWith(MockitoJUnitRunner.class), 则可以使用下面的写法, 原来 Mockito JUnit runner 是这么个用途.
MockitoAnnotations.initMocks(this);
Mockito @Mock vs @InjectMocks Annotations
https://howtodoinjava.com/mockito/mockito-mock-injectmocks/
MainClass使用@InjectMocks, DatabaseDAO都是在MainClass里引用的bean, 可以使用Mock进行模拟注入.
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class ApplicationTest
{
@InjectMocks
MainClass mainClass;
@Mock
DatabaseDAO dependentClassOne;
@Mock
NetworkDAO dependentClassTwo;
@Before
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void validateTest()
{
//record expectations with mock methods
when(dependentClassOne.save()).thenReturn(true);
when(dependentClassTwo.save()).thenReturn(true);
boolean saved = mainClass.save("temp.txt");
assertEquals(true, saved);
//verify recorded expectations
}
}
junit4 ExpectedException用法
https://junit.org/junit4/javadoc/4.12/org/junit/rules/ExpectedException.html
public class SimpleExpectedExceptionTest {
@Rule
public ExpectedException thrown= ExpectedException.none();
@Test
public void throwsNothing() {
// no exception expected, none thrown: passes.
}
@Test
public void throwsExceptionWithSpecificType() {
thrown.expect(NullPointerException.class);
throw new NullPointerException();
}
@Test
public void throwsException() {
thrown.expect(NullPointerException.class);
thrown.expectMessage("happened");
throw new NullPointerException("What happened?");
}
@Test
public void throwsExceptionWhoseCauseCompliesWithMatcher() {
NullPointerException expectedCause = new NullPointerException();
thrown.expectCause(is(expectedCause));
throw new IllegalArgumentException("What happened?", cause);
}
}
junit5 assert exceptions用法
https://www.baeldung.com/junit-assert-exception
JUnit 5 Jupiter assertions API introduces the assertThrows method for asserting exceptions.
@Test
public void whenExceptionThrown_thenAssertionSucceeds() {
Exception exception = assertThrows(NumberFormatException.class, () -> {
Integer.parseInt("1a");
});
String expectedMessage = "For input string";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}
ranger中的mock写法
juni4, 历史版本.
XUserREST 被设置了@InjectMocks
@RunWith(MockitoJUnitRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestXUserREST {
@Rule
public ExpectedException thrown = ExpectedException.none();
@InjectMocks
XUserREST xUserRest = new XUserREST();
VXUser vxUser=createVXUser();
Long id=1L;
@Mock XUserMgr xUserMgr;
@Mock VXGroup vxGroup;
...
}
mock导致所有返回都是null
xUserRest里使用了xUserService, xUserService使用了daoManager, daoManager使用了XXUserDao
xUserRest使用了InjectMocks, 其他的都是@Mocks.
所以下面的代码里配置的 xXUserDao mock其实无法生效, 在更上一级的@Mock(xUserService)就已经拦截了. 而由于没有定义xUserService的mock行为(mock.when.then), 导致所有操作都是返回null,于是整段测试代码一直都返回NullPointerException.
@Test
public void test118deleteUsersWhenNotFound() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getParameter("forceDelete")).thenReturn("true");
Mockito.when(xXUserDao.findByUserName("ABC")).thenReturn(null);
try {
xUserRest.deleteXUserByUserName("ABC", request);
} catch (NullPointerException ex) {
System.out.println("exceptx");
}
Mockito.verify(xUserService).getXUserByUserName("ABC");
Mockito.verify(xXUserDao).findByUserName("ABC");
}
response:
Wanted but not invoked:
xXUserDao.findByUserName("ABC");
-> at org.apache.ranger.rest.TestXUserREST.test118deleteUsersWhenNotFound(TestXUserREST.java:2143)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
xXUserDao.findByUserName("ABC");
-> at org.apache.ranger.rest.TestXUserREST.test118deleteUsersWhenNotFound(TestXUserREST.java:2143)
Actually, there were zero interactions with this mock.
at org.apache.ranger.rest.TestXUserREST.test118deleteUsersWhenNotFound(TestXUserREST.java:2143)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:44)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:258)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:74)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
没有报错的版本, 但其实也有问题.
thrown.expect(NullPointerException.class);看起来能把指定的exception吞掉, 不过下面的verify好像就没有严格检查了, 上一步测试报错xXUserDao没有执行, 在这里就没有报错.
@Test
public void test118deleteUsersWhenNotFound() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getParameter("forceDelete")).thenReturn("true");
Mockito.when(xXUserDao.findByUserName("ABC")).thenReturn(null);
thrown.expect(NullPointerException.class);
xUserRest.deleteXUserByUserName("ABC", request);
Mockito.verify(xUserService).getXUserByUserName("ABC");
Mockito.verify(xXUserDao).findByUserName("ABC");
}
junit4 expect exception
ranger测试里, 使用ExpectedException来验证测试中出现的报错, 不过这也是一个被标记deprecated过时的用法了.
@Rule
public ExpectedException thrown = ExpectedException.none();
正确用法:
@Test
public void test117deleteUsersWhenNotFound() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getParameter("forceDelete")).thenReturn("true");
Mockito.when(xXUserDao.findByUserName(anyString())).thenReturn(null);
// thrown.expect(WebApplicationException.class);
thrown.expect(NullPointerException.class);
xUserRest.deleteXUserByUserName("fakeUserName", request);
}
两个重叠在一起, 看起来是一个与的关系.
@Test
public void test117deleteUsersWhenNotFound() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getParameter("forceDelete")).thenReturn("true");
Mockito.when(xXUserDao.findByUserName(anyString())).thenReturn(null);
thrown.expect(WebApplicationException.class);
thrown.expect(NullPointerException.class);
xUserRest.deleteXUserByUserName("fakeUserName", request);
}
运行后, 要求是同时出现WebApplicationException和NullPointerException, 只有把WebApplicationException注释掉才能测试运行.
11:18:55.125 [main] ERROR org.apache.ranger.authorization.hadoop.config.RangerAdminConfig - Could not add ranger-admin resources to RangerAdminConfig.
11:18:55.125 [main] DEBUG org.apache.ranger.authorization.hadoop.config.RangerAdminConfig - <== addAdminResources(), result=false
11:18:55.128 [main] INFO org.apache.ranger.common.db.BaseDao - ranger.admin.dao.batch.delete.batch.size=1000
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 10.564 sec <<< FAILURE! - in org.apache.ranger.rest.TestXUserREST
test117deleteUsersWhenNotFound(org.apache.ranger.rest.TestXUserREST) Time elapsed: 10.429 sec <<< FAILURE!
java.lang.AssertionError:
Expected: (an instance of javax.ws.rs.WebApplicationException and an instance of java.lang.NullPointerException)
but: an instance of javax.ws.rs.WebApplicationException <java.lang.NullPointerException> is a java.lang.NullPointerException
Stacktrace was: java.lang.NullPointerException
at org.apache.ranger.rest.XUserREST.deleteXUserByUserName(XUserREST.java:867)
at org.apache.ranger.rest.TestXUserREST.test117deleteUsersWhenNotFound(TestXUserREST.java:2122)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)