代码之家  ›  专栏  ›  技术社区  ›  jaco0646

如何测试Spring JPA审计注释?

  •  0
  • jaco0646  · 技术社区  · 4 年前

    我在使用内存中的H2数据库测试SpringJPA(2.5.4)中的审计注释时遇到了问题。我有一个用注释的主类 @EnableJpaAuditing ,以及我的实体的基类。

    @Getter
    @Setter
    @MappedSuperclass
    @EntityListeners(AuditingEntityListener.class)
    public class AuditedEntity {
        @CreatedDate
        LocalDateTime createdDate;
    
        @LastModifiedDate
        LocalDateTime lastModifiedDate;
    }
    

    有两个实体扩展基类:父类和子类。

    @Data
    @Entity
    @Table(name = "one2many")
    class OneToManyEntity extends AuditedEntity {
        @Id
        @GeneratedValue(strategy = SEQUENCE)
        Integer id;
    
        @OneToMany(mappedBy = "parent", cascade = ALL, orphanRemoval = true)
        List<ManyToOneEntity> children;
    }
    
    @Data
    @Entity
    @Table(name = "many2one")
    class ManyToOneEntity extends AuditedEntity {
        @Id
        @GeneratedValue(strategy = SEQUENCE)
        Integer id;
    
        @ManyToOne(optional = false, fetch = LAZY)
        OneToManyEntity parent;
    }
    

    父实体的存储库是一个简单的接口声明。

    @Repository
    interface OneToManyRepository extends CrudRepository<OneToManyEntity, Integer> {
    }
    

    我有几个斯波克测试。

    class OneToManyRepoSpec extends Specification {
        @Autowired
        OneToManyRepository repo
    
        def "test ID is assigned"() {
            given:
                def parent = new OneToManyEntity()
                parent.setChildren([new ManyToOneEntity()])
            expect:
                def persisted = repo.save(parent)
                persisted.getId() > 0
                persisted.getLastModifiedDate() != null
        }
    
        def "LastModifiedDate value is updated"() {
            given:
                def persisted1 = repo.save(new OneToManyEntity())
                sleep(1000)
                persisted1.setChildren([])
                def persisted2 = repo.save(persisted1)
            expect:
                persisted2.lastModifiedDate.isAfter(persisted1.lastModifiedDate)
        }
    }
    

    我可以通过这些测试中的任何一个,这取决于我如何注释测试类;但我无法同时通过这两项测试。

    • 如果我用注释测试类 @DataJpaTest 第一个测试通过(分配了ID和审核值),但第二个测试失败(审核值不更新)。
    • 如果我用注释测试类 @SpringBootTest(webEnvironment = NONE) 第一次测试失败( ConstraintViolationException: NULL not allowed for column "parent_id"; 所以ID没有被分配),但是第二测试通过(审计值被更新)。

    我是否必须将这些测试划分为具有不同注释的不同类,或者有没有一种方法可以将它们放在一起并同时通过?我也有兴趣了解更多关于这些单独测试失败的原因。

    0 回复  |  直到 4 年前
        1
  •  1
  •   kriegaex    4 年前

    正如我所说,我不是Spring用户,但在使用MCVE时,我注意到了以下几点:

    • 对于 @DataJpaTest ,不仅 persisted2 == persisted1 是真的,但是 persisted2 === persisted1 也就是说,对象已更改到位,没有创建新实例。因此,检查 persisted2.lastModifiedDate.isAfter(persisted1.lastModifiedDate) 永远无法工作。
    • 对于 @数据JpaTest , lastModifiedDate 从不更新。也许这种测试并不是为了检查时间戳。即你甚至不能使用 def lastModifiedDate = persisted1.lastModifiedDate 在保存第二次及以后 persisted2.lastModifiedDate.isAfter(lastModifiedDate) 。它也失败了。

    所以你真的应该使用 @SpringBootTest ,如果你想检查这样的时间戳。然而,您需要满足父子关系的参照完整性。如果有修改的选项 @数据JpaTest 为了行为也更新时间戳,我不知道。但这可能是JPA测试中被嘲笑的数据库功能。在春天更有经验的人也许可以回答这个问题。


    使现代化 这样的东西应该对你有用:

    package spring.jpa
    
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.boot.test.context.SpringBootTest
    import spock.lang.Specification
    
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
    class AuditingSpec extends Specification {
      @Autowired
      OneToManyRepository repo
    
      def "New parent and child are both assigned IDs and dates"() {
        given:
        def parent = new OneToManyEntity()
        parent.setChildren([createChild(parent)])
        when:
        def persisted = repo.save(parent)
        then:
        def persistedChild = persisted.children.first()
        persisted.createdDate
        persisted.createdDate == persisted.lastModifiedDate
        persistedChild.createdDate
        persistedChild.createdDate == persistedChild.lastModifiedDate
      }
    
      def "Appended child is assigned IDs and dates"() {
        given:
        def parent = new OneToManyEntity()
        parent.setChildren([createChild(parent)])
        def persisted = repo.save(parent)
        persisted.children.add(createChild(parent))
        when:
        def persisted2 = repo.save(persisted)
        then:
        persisted2.children.size() == 2
        def firstChild = persisted2.children.first()
        def secondChild = persisted2.children.last()
        secondChild.id > firstChild.id
        secondChild.createdDate
        secondChild.createdDate == secondChild.lastModifiedDate
      }
    
      def "LastModifiedDate value is updated"() {
        given:
        def persisted1 = repo.save(new OneToManyEntity())
        //sleep(1000)
        persisted1.setChildren([])
        def persisted2 = repo.save(persisted1)
        expect:
        persisted2.lastModifiedDate.isAfter(persisted1.lastModifiedDate)
      }
    
      static ManyToOneEntity createChild(OneToManyEntity parent) {
        def child = new ManyToOneEntity()
        child.setParent(parent)
        child
      }
    }
    
        2
  •  0
  •   jaco0646    4 年前

    多亏了评论中的提示,我意识到这两个测试在相反的场景中失败了,因为第一个测试必须在事务中运行,而第二个测试必须 不 在事务中运行。测试失败表现为不同的类级别注释,因为 @DataJpaTest 是事务性的,而 @SpringBootTest 不是。因此,解决方案是使用 @SpringBootTest 并仅将第一个测试注释为 @Transactional 。我已经更新了GitHub project 照着

        3
  •  0
  •   Marko    4 年前

    对于任何对场景有问题的人来说,f.e.@CreatedDate不是在junit测试中存储在DB中后生成的。只需将@DataJpaTest与@EnableJpaAuditing一起使用,您的@CreatedDate和其他生成的字段也将开始在junit测试中工作。在我的案例中缺少的是@EnableJpaAuditing注释@DataJpaTest默认为未启用jpa审核,因此使用annotation@EnableJpaAuditing可以启用它。祝你好运:)

    推荐文章