Java单元测试实践-16.SpringAOP与Mock
1. Spring AOP与Mock
java replace方法以下⽰例使⽤CGLIB代理,或JDK动态代理,执⾏结果相同。
以下使⽤注解的⽅式设置AOP,对⽅法或⾃定义注解设置AOP的效果相同。
后续内容在设置AOP,对⽅法设置了AOP。对⾃定义注解设置AOP的处理可参考⽰例TestSpAOPARawGet、TestSpAOPARun类。
1.1. 查看AOP代理对象信息
通过@Autowired等注解获得被AOP处理的对象时,获取到的对象为AOP代理对象,⽆法直接获取到原始对象。
代理对象类名
AOP代理对象不是原始类的实例。
当Spring AOP使⽤JDK动态代理时,AOP代理对象的类名⽰例如下:
com.sun.proxy.$Proxy36
当Spring AOP使⽤CGLIB代理时,AOP代理对象的类名⽰例如下:
com.adrninistrator.service.impl.TestAOPService1Impl$$EnhancerBySpringCGLIB$$1cb17163
当执⾏AOP代理对象的⽅法时,会先经过对应的Aspect的处理。
代理对象中的成员变量
当使⽤JDK动态代理时,由于代理对象中不包含成员变量,在获取代理对象中的成员变量时会出现异常,异常信息如下所⽰。
ptions.FieldNotFoundException
No instance field of type "com.adrninistrator.service.TestService2" could be found in the class hierarchy of com.sun.proxy.$Proxy46.
当使⽤CGLIB代理时,获取代理对象中的成员变量,结果为空。
可参考⽰例TestSpAOPMProxyInfo1类。
1.2. 获取代理对象对应的原始对象
使⽤TargetSource().getTarget()⽅法
当需要获取AOP代理对象对应的原始对象时,可以使⽤TargetSource().getTarget()⽅法,⽰例如下:
@Autowired
private TestAOPService1 testAOPService1;
if(!AopUtils.isAopProxy(testAOPService1)||!(testAOPService1 instanceof Advised)){
fail("illegal");
}
Advised advised =(Advised) testAOPService1;
TestAOPService1 testAOPService1Raw =(TestAOPService1) TargetSource().getTarget();
在获取到原始对象后,调⽤原始对应的⽅法时,不会经过对应的Aspect的处理。
可参考⽰例TestSpAOPMRawGet1类。
使⽤TargetObject()⽅法
使⽤TargetObject()⽅法也可以获取AOP代理对象对应的原始对象,相⽐使⽤
@Autowired
private TestAOPService1 testAOPService1;
TestAOPService1 testAOPService1Raw2 = TargetObject(testAOPService1);
使⽤TargetSource().getTarget()⽅法与使⽤TargetObject()⽅法获取原始对象的效果相同,获取到的为同⼀个对象。
可参考⽰例TestSpAOPMRawGet2类。
1.3. 将被引⽤的AOP代理对象替换为原始对象
通过上述⽅式获取AOP代理对象的原始对象后,可以将代理对象替换为原始对象,跳过Aspect的处理。⽰例如下,可参考⽰例TestSpAOPMRawGetUse1类。
@Autowired
private TestAOPService1 testAOPService1;
@Autowired
private TestAOPService2 testAOPService2;
TestAOPService1 testAOPService1Raw = TargetObject(testAOPService1);
Whitebox.setInternalState(testAOPService2, testAOPService1Raw);
1.4. 将被引⽤的AOP代理对象替换为Mock对象
可将被测试类中的AOP代理对象替换为Mock对象,当被测试类调⽤Mock对象的⽅法时,不会经过对应的Aspect的处理,且可对Mock对象的⽅法进⾏Stub。⽰例如下,可参考⽰例TestSpAOPMProxyMock1类。
@Autowired
private TestAOPService2 testAOPService2;
TestAOPService1 testAOPService1Mock = k(TestAOPService1.class);
Mockito.stAround(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
Whitebox.setInternalState(testAOPService2, testAOPService1Mock);
1.5. 将被引⽤的AOP代理对象替换为Spy对象
可将被测试类中的AOP代理对象替换为Spy对象,当被测试类调⽤Spy对象的⽅法时,不会经过对应的Aspect的处理,且可对Spy对象的⽅法进⾏Stub。⽰例如下,可参考⽰例TestSpAOPMProxySpy1类。
@Autowired
private TestAOPService1 testAOPService1;
@Autowired
private TestAOPService2 testAOPService2;
TestAOPService1 testAOPService1Raw = TargetObject(testAOPService1);
TestAOPService1 testAOPService1Spy = Mockito.spy(testAOPService1Raw);
Mockito.doReturn(TestConstants.FLAG1).when(testAOPService1Spy).testAround(TestConstants.FLAG1);
Whitebox.setInternalState(testAOPService2, testAOPService1Spy);
1.6. 对Aspect进⾏Stub/Replace
在调⽤被AOP处理的类时,若需要跳过Aspect处理,可⾏⽅法除对AOP代理对象进⾏替换外,还可以对Aspect的⽅法通过PowerMockito.stub()、place()等⽅法进⾏Stub/Replace,改变Aspect的操作。可参考⽰例TestSpAOPMStubAspect1类。
1.7. 对原始对象进⾏Stub/Replace
在对AOP原始对象对应的类进⾏Stub/Replace时,不会对Aspect产⽣影响,Aspect仍⽣效。在调⽤AOP对象的⽅法时,仍会先经过Aspect的处理。可参考⽰例TestSpAOPMStubTarget1类。
1.8. 替换AOP原始对象中的成员变量
当需要将被AOP处理的原始对象中的成员变量替换为Mock/Spy对象时,不能对AOP代理对象进⾏成员变量替换。
当使⽤JDK动态代理时,由于代理对象中不包含成员变量,执⾏替换操作时会出现异常,异常信息如下所⽰。
ptions.FieldNotFoundException
No instance field assignable from "com.adrninistrator.service.TestService2$MockitoMock$1618418885" could be found in the class hierarchy of com.sun.p roxy.$Proxy46.
当使⽤CGLIB代理时,执⾏替换操作不会出现异常,但在执⾏AOP代理对象的⽅法时并不会调⽤被替换的成员变量。
可参考⽰例TestSpAOPMRawMockMemWrong1、TestSpAOPMRawSpyMemWrong1类。
当需要将被AOP处理的原始对象中的成员变量替换为Mock/Spy对象时,应当先获取AOP代理对象对应的原始对象,再将原始对象的成员变量替换为Mock/Spy对象。当直接或间接调⽤AOP代理对象的⽅法时( 其中调⽤了成员变量的⽅法 ),会调⽤成员变量的Mock/Spy对象对应的⽅法。⽰例如下:
@Autowired
private TestAOPService1 testAOPService1;
TestService2 testService2Mock = k(TestService2.class);
Mockito.st1(TestConstants.FLAG3)).thenReturn(TestConstants.MOCKED);
TestAOPService1 testAOPService1Raw = TargetObject(testAOPService1);
Whitebox.setInternalState(testAOPService1Raw, testService2Mock);
可参考⽰例TestSpAOPMRawMockMem1、TestSpAOPMRawSpyMem1类。
2. 对使⽤了事务的类进⾏Mock
对于声明使⽤事务的类,例如使⽤@Transactional注解等⽅式,其对应的对象会被处理为代理,通过@Autowired等注解获得使⽤事务的类的对象时,获取到的对象为代理对象。
获得使⽤事务的类的对象后,通过其Class对象,可以判断其为代理对象,说明可参考前⽂,可参考⽰例TestDatabaseTxInfo类。
以下所述的被测试代码,在事务中先后执⾏了数据库操作1与数据库操作2。
为了测试被测试代码使⽤了事务处理的数据库操作是否正确,当操作2出现异常时是否出现异常时是否会将操作1回滚,可对Mybatis的Mapper对象进⾏Mock并替换⾄被测试类中,对Mapper的Mock对象对象进⾏Stub,使其执⾏数据库操作1时调⽤原始Mapper对象的⽅法,执⾏操作2时抛出异常。通过以上处理,可以构造出使⽤事务的⽅法出现异常的场景,之后可以检查数据库记录是否符合预期。可参考⽰例TestDatabaseTxMockMem类。
当需要对使⽤事务的类的实例的成员变量进⾏替换时,需要获取代理对象对应的原始对象后再进⾏替换操作,可使⽤
上述⽰例如下:
@Autowired
private TestTxService1 testTxService1;
@Autowired
private TestTableMapper testTableMapper;
TestTxService1 testTxService1Raw = TargetObject(testTxService1);
TestTableMapper testTableMapperMock = placeMockMember(testTxService1Raw, TestTableMapper.class);
Mockito.when(testTableMapperMock.insert(Mockito.any(TestTableEntity.class))).thenAnswer(invocation ->{
TestTableEntity arg1 = Argument(0);
return testTableMapper.insert(arg1);
});
Mockito.when(testTableMapperMock.updateByPrimaryKeySelective(Mockito.any(TestTableEntity.class))).thenThrow
(new RuntimeException(TestConstants.MOCKED));
Mockito.when(testTableMapperMock.selectByPrimaryKey(Mockito.anyString())).thenAnswer(invocation -
>{
String arg1 = Argument(0);
return testTableMapper.selectByPrimaryKey(arg1);
});
3. 对使⽤了@Async注解的类进⾏Mock
对于被指定了@Async注解的⽅法,会以异步⽅式执⾏,对应类的对象会被处理为代理,通过@Autowired等注解获得使⽤了@Async注解的类的对象时,获取到的对象为代理对象。
为了使@Async注解⽣效,可以使⽤@EnableAsync注解,或在Spring的XML⽂件中通过task:annotation-driven进⾏配置,可通过executor参数指定线程池,通过proxy-target-class参数指定是否使⽤CGLIB代理(true:使⽤CGLIB代理,false:使⽤JDK动态代理,默认false),如下所⽰:
<annotation-driven executor="testThreadPoolTaskExecutor"proxy-target-class="true"/>
对使⽤了@Async注解的类进⾏Mock,与使⽤了AOP或事务的类处理类似,说明可参考前⽂,可参考⽰例TestAsyncRawGet、TestAsyncRun类。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。