Java单元测试实践-16.Spring AOP与Mock

Java单元测试实践-00.目录(9万多字文档+700多测试示例)
https://blog.csdn.net/a82514921/article/details/107969340

1. Spring AOP与Mock

以下示例使用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动态代理时,由于代理对象中不包含成员变量,在获取代理对象中的成员变量时会出现异常,异常信息如下所示。

org.powermock.reflect.exceptions.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. 获取代理对象对应的原始对象

  • 使用Advised.getTargetSource().getTarget()方法

当需要获取AOP代理对象对应的原始对象时,可以使用Advised.getTargetSource().getTarget()方法,示例如下:

@Autowired
private TestAOPService1 testAOPService1;

if (!AopUtils.isAopProxy(testAOPService1) || !(testAOPService1 instanceof Advised)) {
    fail("illegal");
}

Advised advised = (Advised) testAOPService1;

TestAOPService1 testAOPService1Raw = (TestAOPService1) advised.getTargetSource().getTarget();

在获取到原始对象后,调用原始对应的方法时,不会经过对应的Aspect的处理。

可参考示例TestSpAOPMRawGet1类。

  • 使用AopTestUtils.getTargetObject()方法

使用AopTestUtils.getTargetObject()方法也可以获取AOP代理对象对应的原始对象,相比使用Advised.getTargetSource().getTarget()方法更简单,示例如下:

@Autowired
private TestAOPService1 testAOPService1;

TestAOPService1 testAOPService1Raw2 = AopTestUtils.getTargetObject(testAOPService1);

使用Advised.getTargetSource().getTarget()方法与使用AopTestUtils.getTargetObject()方法获取原始对象的效果相同,获取到的为同一个对象。

可参考示例TestSpAOPMRawGet2类。

1.3. 将被引用的AOP代理对象替换为原始对象

通过上述方式获取AOP代理对象的原始对象后,可以将代理对象替换为原始对象,跳过Aspect的处理。示例如下,可参考示例TestSpAOPMRawGetUse1类。

@Autowired
private TestAOPService1 testAOPService1;

@Autowired
private TestAOPService2 testAOPService2;

TestAOPService1 testAOPService1Raw = AopTestUtils.getTargetObject(testAOPService1);

Whitebox.setInternalState(testAOPService2, testAOPService1Raw);

1.4. 将被引用的AOP代理对象替换为Mock对象

可将被测试类中的AOP代理对象替换为Mock对象,当被测试类调用Mock对象的方法时,不会经过对应的Aspect的处理,且可对Mock对象的方法进行Stub。示例如下,可参考示例TestSpAOPMProxyMock1类。

@Autowired
private TestAOPService2 testAOPService2;

TestAOPService1 testAOPService1Mock = Mockito.mock(TestAOPService1.class);

Mockito.when(testAOPService1Mock.testAround(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 = AopTestUtils.getTargetObject(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()、PowerMockito.replace()等方法进行Stub/Replace,改变Aspect的操作。可参考示例TestSpAOPMStubAspect1类。

1.7. 对原始对象进行Stub/Replace

在对AOP原始对象对应的类进行Stub/Replace时,不会对Aspect产生影响,Aspect仍生效。在调用AOP对象的方法时,仍会先经过Aspect的处理。可参考示例TestSpAOPMStubTarget1类。

1.8. 替换AOP原始对象中的成员变量

当需要将被AOP处理的原始对象中的成员变量替换为Mock/Spy对象时,不能对AOP代理对象进行成员变量替换。

当使用JDK动态代理时,由于代理对象中不包含成员变量,执行替换操作时会出现异常,异常信息如下所示。

org.powermock.reflect.exceptions.FieldNotFoundException
No instance field assignable from "com.adrninistrator.service.TestService2$MockitoMock$1618418885" could be found in the class hierarchy of com.sun.proxy.$Proxy46.

当使用CGLIB代理时,执行替换操作不会出现异常,但在执行AOP代理对象的方法时并不会调用被替换的成员变量。

可参考示例TestSpAOPMRawMockMemWrong1、TestSpAOPMRawSpyMemWrong1类。

当需要将被AOP处理的原始对象中的成员变量替换为Mock/Spy对象时,应当先获取AOP代理对象对应的原始对象,再将原始对象的成员变量替换为Mock/Spy对象。当直接或间接调用AOP代理对象的方法时( 其中调用了成员变量的方法 ),会调用成员变量的Mock/Spy对象对应的方法。示例如下:

@Autowired
private TestAOPService1 testAOPService1;

TestService2 testService2Mock = Mockito.mock(TestService2.class);
Mockito.when(testService2Mock.test1(TestConstants.FLAG3)).thenReturn(TestConstants.MOCKED);

TestAOPService1 testAOPService1Raw = AopTestUtils.getTargetObject(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类。

当需要对使用事务的类的实例的成员变量进行替换时,需要获取代理对象对应的原始对象后再进行替换操作,可使用AopTestUtils.getTargetObject()方法,说明可参考前文,可参考示例TestDatabaseTxMockMem类,反例可参考示例TestDatabaseTxMockMemWrong类。

上述示例如下:

@Autowired
private TestTxService1 testTxService1;

@Autowired
private TestTableMapper testTableMapper;


TestTxService1 testTxService1Raw = AopTestUtils.getTargetObject(testTxService1);

TestTableMapper testTableMapperMock = TestReplaceUtil.replaceMockMember(testTxService1Raw, TestTableMapper.class);

Mockito.when(testTableMapperMock.insert(Mockito.any(TestTableEntity.class))).thenAnswer(invocation -> {
	TestTableEntity arg1 = invocation.getArgument(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 = invocation.getArgument(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),如下所示:

<task:annotation-driven executor="testThreadPoolTaskExecutor" proxy-target-class="true"/>

对使用了@Async注解的类进行Mock,与使用了AOP或事务的类处理类似,说明可参考前文,可参考示例TestAsyncRawGet、TestAsyncRun类。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值