在测试类上使用@RunWith
和ContextConfiguration
进行修饰,指定Spring的配置文件即可使用单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestClass {
@Test
public void test1() {}
}
在spring单元测试中,默认不支持事务,如果需要单元测试类支持事务,则需要继承AbstractTransactionalJUnit4SpringContextTests
类,同时需要在配置文件中配置事务管理器。
xml配置:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
单元测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestClass extends AbstractTransactionalJUnit4SpringContextTests {
@Test
public void test1() {}
}
因为在spring单元测试中,默认会在方法的开始之前开启事务,当方法执行成功或失败以后回滚事务。
所有我们在单元测试方法中修改的数据可以在同一个方法中查询到,但是并不会体现在数据库上面。
在AbstractTransactionalJUnit4SpringContextTests
的源码中有下面一段:
* @see org.springframework.test.annotation.Commit
* @see org.springframework.test.annotation.Rollback
我们查看Commit
和Rollback
注解的源码注释可以给出解释:
Commit:
* {@code @Commit} is a test annotation that is used to indicate that a
* <em>test-managed transaction</em> should be <em>committed</em> after
* the test method has completed.
Rollback:
* {@code @Rollback} is a test annotation that is used to indicate whether
* a <em>test-managed transaction</em> should be <em>rolled back</em> after
* the test method has completed.
上面是Spring官方给出的解释,简单的说就是,当在测试类或测试方法上标注@Commit
或者@Rollback
时,就会决定当前的事务是提交还是回滚。
看到这里,我们的解决方案就出来了,只需要在我们的测试类或者测试方法上标注@Commit
或者@Rollback(false)
就可以实现事务的提交。
Spring单元测试实现事务管理主要类为TransactionalTestExecutionListener
。
Spring在当前类中进行开启事务,同时检测事务是否需要回滚。在该类中开始事务的方法为beforeTestMethod
方法。
public class TransactionalTestExecutionListener extends AbstractTestExecutionListener {
//...
public void beforeTestMethod(final TestContext testContext) throws Exception {
// ...
if (tm != null) {
// 在此创建一个事务上下文,用于管理事务的提交或回滚
txContext = new TransactionContext(testContext, tm, transactionAttribute, isRollback(testContext));
runBeforeTransactionMethods(testContext); // 运行事务之前的代码
txContext.startTransaction(); // 开启事务
TransactionContextHolder.setCurrentTransactionContext(txContext);
}
// ...
}
// ...
}
从上面的代码可以看出,Spring创建了一个TransactionContext
用于管理事务的声明周期。同时在该方法中开启事务,并将事务绑定到当前单元测试的上下文上。
同时可以注意到isRollback()
方法,Spring通过该方法检测单元测试类或方法上是否存在Rollback
注解。
// 检测是否需要回滚
protected final boolean isRollback(TestContext testContext) throws Exception {
// 检测回滚测试是否是默认项
boolean rollback = isDefaultRollback(testContext);
// 获取类或方法上的注解
Rollback rollbackAnnotation =
AnnotatedElementUtils.findMergedAnnotation(testContext.getTestMethod(), Rollback.class);
if (rollbackAnnotation != null) {
// 获取到覆盖默认策略
boolean rollbackOverride = rollbackAnnotation.value();
rollback = rollbackOverride;
}
return rollback;
}
}
// 检测是否是默认的回滚策略
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
Class<?> testClass = testContext.getTestClass();
// 获取类或方法上的Rollback注解
Rollback rollback = AnnotatedElementUtils.findMergedAnnotation(testClass, Rollback.class);
boolean rollbackPresent = (rollback != null);
// ...
if (rollbackPresent) {
// Rollback 注解存在则返回注解内的 value 值
boolean defaultRollback = rollback.value();
return defaultRollback;
}
// else
return txConfigAttributes.isDefaultRollback();
}
isRollback()
方法中首先检测事务回滚策略是否是默认策略。然后再次检测单元测试类或方法上是否存在Rollback
注解,如果有注解,则覆盖默认的回滚策略。
通过上面的注解检测阶段我们看到了一段代码:
txContext = new TransactionContext(testContext, tm, transactionAttribute, isRollback(testContext));
由此我们可以确定,事务的管理应该就是在当前类中进行管理的。下面我们看下当前调用的构造器:
TransactionContext(TestContext testContext,
PlatformTransactionManager transactionManager,
TransactionDefinition transactionDefinition, boolean defaultRollback) {
this.testContext = testContext;
this.transactionManager = transactionManager;
this.transactionDefinition = transactionDefinition;
this.defaultRollback = defaultRollback;
this.flaggedForRollback = defaultRollback;
}
对比构造器的调用和声明,可以看出检测Rollback
注解获取到的值,在构造器中被赋值给flaggedForRollback
属性。通过跟踪flaggedForRollback
属性,在endTransaction()
方法中可以看到如下代码:
void endTransaction() {
if (this.flaggedForRollback) {
this.transactionManager.rollback(this.transactionStatus);
}
else {
this.transactionManager.commit(this.transactionStatus);
}
}
至此,也就可以很容易的看出,为什么Rollback
注解可以控制事务的提交或者回滚了。
在前面的源码解析中检测和获取的注解值都是Rollback
,那Commit
就没有作用了吗?
带着这个疑问,我们可以看下Commit
注解的声明:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Rollback(false)
public @interface Commit {
}
看到源码豁然开朗,原来Commit
只是一个标记注解,它最主要的作用就是承载@Rollback(false)
。
这样在注解搜索阶段就可以通过获取类或方法上的所有注解,同时获取注解的注解,也就是说,spring通过检测类或者方法上的所有注解,同时检测获取到的注解有没有被Rollback
注解进行修饰。通过这种方法,在注解检测阶段就不需要分别对这两类注解分别进行解析。