如何测试 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;但我无法同时通过这两项测试。
- 如果我用
@DataJpaTest
注释测试 class,第一个测试通过(分配 ID 和审核值)但第二个测试失败(未更新审核值)。
- 如果我用
@SpringBootTest(webEnvironment = NONE)
注释测试 class,第一个测试失败(ConstraintViolationException: NULL not allowed for column "parent_id";
所以没有分配 ID)但第二个测试通过(更新审计值)。
我是否必须将这些测试拆分成不同的 classes 并使用不同的注释,或者是否有办法将它们放在一起并同时通过?我也有兴趣了解更多有关导致这些单独测试失败的原因。
正如我所说,我不是 Spring 用户,但我在玩你的 MCVE 时注意到以下几点:
- 对于
@DataJpaTest
,不仅persisted2 == persisted1
为真,甚至persisted2 === persisted1
也为真。即,对象就地更改,没有创建新实例。因此,检查 persisted2.lastModifiedDate.isAfter(persisted1.lastModifiedDate)
永远无法工作。
- 对于
@DataJpaTest
,lastModifiedDate
永远不会更新。也许那种测试并不意味着检查时间戳。 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 您将启用它。祝你好运:)
我在使用 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;但我无法同时通过这两项测试。
- 如果我用
@DataJpaTest
注释测试 class,第一个测试通过(分配 ID 和审核值)但第二个测试失败(未更新审核值)。 - 如果我用
@SpringBootTest(webEnvironment = NONE)
注释测试 class,第一个测试失败(ConstraintViolationException: NULL not allowed for column "parent_id";
所以没有分配 ID)但第二个测试通过(更新审计值)。
我是否必须将这些测试拆分成不同的 classes 并使用不同的注释,或者是否有办法将它们放在一起并同时通过?我也有兴趣了解更多有关导致这些单独测试失败的原因。
正如我所说,我不是 Spring 用户,但我在玩你的 MCVE 时注意到以下几点:
- 对于
@DataJpaTest
,不仅persisted2 == persisted1
为真,甚至persisted2 === persisted1
也为真。即,对象就地更改,没有创建新实例。因此,检查persisted2.lastModifiedDate.isAfter(persisted1.lastModifiedDate)
永远无法工作。 - 对于
@DataJpaTest
,lastModifiedDate
永远不会更新。也许那种测试并不意味着检查时间戳。 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 您将启用它。祝你好运:)