【问题标题】:Cannot test expected exception when using @Transactional with @Commit将@Transactional 与@Commit 一起使用时无法测试预期异常
【发布时间】:2016-09-21 06:17:29
【问题描述】:

我正在使用 Spring Boot 1.4.0.M3。

我有一个 @Entity 有一个 username 应该是唯一的:

@NotNull
@Column(unique = true)
@Pattern(regexp = "[a-zA-Z_\\-\\.0-9]+")
@Size(min = 1, max = 30)
private String username;

使用 Spring Data Repository,我想测试使用重复用户名时是否会出现异常。此测试有效:

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserRepositoryIntegrationTest {

    @Autowired
    private UserRepository repository;

    @Test(expected = DataIntegrityViolationException.class)
    public void test() {
        repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
        repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
    }
}

但是,将@Transactional@Commit 添加时,此测试失败:

@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class UserRepositoryIntegrationTest {

    @Autowired
    private UserRepository repository;

    @Test(expected = DataIntegrityViolationException.class)
    @Commit
    public void test() {
        repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
        repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
    }
}

但是查看日志记录,DataIntegrityViolationException 被抛出:

2016-05-24 09:05:16.619 错误 22790 --- [主要] o.h.engine.jdbc.spi.SqlExceptionHelper :唯一索引或主键 违规:“UK_D8HGQ87BS4VPMC81NQ9G69G8X_INDEX_D ON PUBLIC.MYPROJECT_USER(USERNAME) VALUES ('wim', 1)"; SQL 语句: 插入 myproject_user(密码、用户名、角色、id)值(?, ?, '管理员', ?) [23505-191] 2016-05-24 09:05:16.620 信息 22790 --- [
主要] o.h.e.j.b.internal.AbstractBatchImpl:HHH000010:发布时 它仍然包含 JDBC 语句 2016-05-24 09:05:16.629 WARN 22790 --- [主] os.test.context.TestContextManager
: 在允许 TestExecutionListener 时捕获异常 [org.springframework.test.context.transaction.TransactionalTestExecutionListener@589b3632] 处理“后”执行测试:方法 [public void com.spring.boot.test.user.UserRepositoryIntegrationTest.test()], 实例 [com.spring.boot.test.user.UserRepositoryIntegrationTest@3ca278bc], 异常 [java.lang.AssertionError:预期异常: org.springframework.dao.DataIntegrityViolationException]

为什么测试失败?会不会是 JUnit 在 Spring 提交事务之前检查是否抛出异常?

【问题讨论】:

  • 正如预期的那样......该异常将在事务提交之后发生。事务在方法结束后提交。所以基本上在检查的那一刻没有例外。你用这个改变了测试的行为。你需要在你的方法中提交。
  • “在允许 TestExecutionListener 时捕获异常”说明了一切。你想提交,但你想断言提交失败。 JUnit 和 Spring 彼此不了解,因此您不能对这些东西使用 JUnit 断言。正如马丁所说,在你的测试中使用TestTransaction#end
  • 感谢您指向该课程。不知道它的存在。我已经添加了完整工作版本的答案。

标签: java spring junit transactions


【解决方案1】:

你不需要提交。您只需要将您的工作单元刷新到数据库中即可看到DataIntegrityViolationException

这在Spring Reference Manual 中关于“误报”的说明中有所描述。但是请注意,EntityManager 必须通过@PersistenceContext(而不是@Autowired)注入。

以下内容应该适合您:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class UserRepositoryIntegrationTest {

    @Autowired
    UserRepository repository;

    @PersistenceContext
    EntityManager entityManager;

    @Test(expected = DataIntegrityViolationException.class)
    public void test() {
        repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
        repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));

        entityManager.flush();
    }
}

问候,

Sam(Spring TestContext 框架的作者

【讨论】:

    【解决方案2】:

    我需要在 MVC 控制器测试 (@WebAppConfiguration) 中测试 DataIntegrityViolationException 并通过 @ControllerAdvice 在应用程序内部进行 @ResponseStatus 修改。所以你的方法不适合我,它刷新为时已晚。不幸的是 @NotTransactional 注释在 Spring 4 中被排除在外,所以我只是将 @Transactional 的测试分开,而不是事务性的。另请参阅来自

    http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/testing.html#integration-testing-annotations

    @NotTransactional 已弃用

    更新。 2种解决方法:

    • 明确区分事务性和非事务性测试
    • 在不需要事务的地方使用@Transactional(propagation = Propagation.NEVER)

    【讨论】:

    • 这不是问题的答案。您可以将此添加为对答案的评论,或针对此问题提出单独的问题。
    • 是的,我同意你的看法。但对于和你有同样问题的人来说,这是一个不错的选择。
    【解决方案3】:

    基于 M.Deinum 和 Stephane Nicoll 的 cmets,这是工作版本:

    @SpringBootTest
    @RunWith(SpringRunner.class)
    @Transactional
    public class UserRepositoryIntegrationTest {
    
        @Autowired
        private UserRepository repository;
    
        @Test(expected = DataIntegrityViolationException.class)
        public void test() {
            repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
            repository.save(User.createAdmin(repository.nextId(), "wim", "123456"));
    
            TestTransaction.flagForCommit();
            TestTransaction.end();
        }
    }
    

    请注意,需要调用这两个静态方法才能使其工作。

    【讨论】:

    • 太棒了,你让我开心,这是完美的答案,简短而甜蜜
    猜你喜欢
    • 2015-08-09
    • 2011-07-31
    • 1970-01-01
    • 2019-01-23
    • 2018-07-21
    • 1970-01-01
    • 2017-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多