如何测试 Spring JPA 审计注解?

How to test Spring JPA audit annotations?

我在使用 H2 内存数据库测试 Spring JPA (2.5.4) 中的审计注释时遇到问题。我有一个用 @EnableJpaAuditing 注释的主 class 和一个用于我的实体的基础 class。

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

    @LastModifiedDate
    LocalDateTime lastModifiedDate;
}

两个实体扩展了基础 class:父项和子项。

@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> {
}

我有几个 Spock 测试。

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)
    }
}

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

我是否必须将这些测试拆分成不同的 classes 并使用不同的注释,或者是否有办法将它们放在一起并同时通过?我也有兴趣了解更多有关导致这些单独测试失败的原因。

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

  • 对于@DataJpaTest,不仅persisted2 == persisted1为真,甚至persisted2 === persisted1也为真。即,对象就地更改,没有创建新实例。因此,检查 persisted2.lastModifiedDate.isAfter(persisted1.lastModifiedDate) 永远无法工作。
  • 对于 @DataJpaTestlastModifiedDate 永远不会更新。也许那种测试并不意味着检查时间戳。 IE。你甚至不能在第二次保存之前使用 def lastModifiedDate = persisted1.lastModifiedDate 和之后的 persisted2.lastModifiedDate.isAfter(lastModifiedDate)。它也失败了。

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


更新:像这样的东西应该适合你:

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
  }
}

感谢评论中的提示,我意识到这两个测试在相反的情况下会失败,因为第一个测试必须 运行 在事务中,而第二个测试必须 运行 在一笔交易中。测试失败显示为不同的 class 级注释,因为 @DataJpaTest 是事务性的,而 @SpringBootTest 不是。所以解决方案是使用 @SpringBootTest 并仅将第一个测试注释为 @Transactional。我已经相应地更新了 GitHub project

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