【问题标题】:How to fake last modified time for unit tests?如何伪造单元测试的最后修改时间?
【发布时间】:2020-12-02 11:02:19
【问题描述】:

我有一个简单的实体,它要求最后修改时间应该在持久时更新。

@Data      // Lombok thing
@Entity
@Table(name = "MY_ENTITY")
public class MyEntity {

    @Column(name = "LAST_MODIFIED", nullable = false)
    private LocalDateTime lastModified;

    // irrelevant columns including id omitted

    @PrePersist
    public void initializeUUID() {
        lastModified = LocalDateTime.now();
    }
}

我需要实现一个查询这些实体的作业早于特定时间(比方说一天),修改其状态并将它们持久化。我在为涵盖此类用例的单元测试创​​建数据时遇到问题。

虽然我手动设置了lastModified 时间,但@PrePersist 会导致其更改,无论设置值如何。

@Autowired  // Spring Boot tests are configured against in-memory H2 database
MyEntityRepository myEntityRepository;
var entity = new MyEntity();
entity.setLastModified(LocalDateTime.now().minusDays(3));
myEntityRepository.entity(entity);

问题:如何准备预先保存的数据 (lastModified) 而不为了单元测试而大幅修改 MyEntity 类?欢迎使用 Mockito 的解决方案。

注意我使用 Spring Boot + jUnit 5 + Mockito

我尝试过的事情:

  • How to mock persisting and Entity with Mockito and jUnit: 模拟持久化实体不是一种方法,因为我需要将实体持久化在 H2 中以进行进一步检查。此外,我尝试使用 spy bean 使用这个技巧 Spring Boot #7033 获得相同的结果。

  • Hibernate Tips: How to activate an entity listener for all entities:使用为单元测试范围配置的静态嵌套类 @TestConfiguration 以编程方式添加侦听器。这个东西根本没有被调用。

    @TestConfiguration
    public static class UnitTestConfiguration {                 // logged as  registered
    
        @Component
        public static class MyEntityListener implements PreInsertEventListener {
    
            @Override
            public boolean onPreInsert(PreInsertEvent event) {  // not called at all
                Object entity = event.getEntity();
                log.info("HERE {}" + entity);                   // no log appears
                // intention to modify the `lastModified` value
                return true;
            }
        }
    
  • 肮脏的方式:使用“覆盖”lastModified 值的 @PrePersist 创建一个扩展 MyEntity 的方法级类。结果为org.springframework.dao.InvalidDataAccessApiUsageException。为了解决这个问题,这样的实体依赖于 @Inheritance 注释 (JPA : Entity extend with entity),我不想仅仅为了单元测试而使用它。实体不得在生产代码中扩展。

【问题讨论】:

  • 您可以选择使用 Spring 的AuditingEntityListener 吗?似乎很适合您的用例。然后,您可以在测试配置中提供替代的 auditingDateTimeProvider bean。
  • @wjans:我从来没有听说过这个,我一定会看看并试一试。你能给我这个东西的示例用法吗(最好是答案)?
  • @priyranjan:我偶然发现了这个问题。只要我不使用依赖项来设置lastModified 值,它就没有提供实现此目的的方法。此外,如果我错了,请纠正我,JMockit 是另一个依赖项,我并不热衷于仅将它包含在一个测试中(我使用 Spring + jUnit 5 + Mockito 组合)。

标签: java spring spring-boot spring-data-jpa spring-test


【解决方案1】:

您可以使用 Spring Data JPA AuditingEntityListener

只需通过@org.springframework.data.jpa.repository.config.EnableJpaAuditing 启用它,并可选择提供自定义dateTimeProviderRef,如下所示:

@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "myAuditingDateTimeProvider")
public class JpaAuditingConfig {

    @Bean(name = "myAuditingDateTimeProvider")
    public DateTimeProvider dateTimeProvider(Clock clock) {
        return () -> Optional.of(now(clock));
    }
}

你的实体可能看起来像这样:

@Data      // Lombok thing
@Entity
@Table(name = "MY_ENTITY")
@EntityListeners(AuditingEntityListener.class)
public class MyEntity {

    @LastModifiedDate
    private LocalDateTime lastModified;

    // irrelevant columns including id omitted
}

在上面的示例中,可以通过 Spring 提供 java.time.Clock,这已经可以解决您有关测试的问题。但你也可以提供一个专用的测试配置,指定一个不同的/模拟的DateTimeProvider

请注意,这里提到的解决方案不是纯粹的单元测试方法。但根据您的问题和您尝试过的事情,我得出的结论是使用 Spring 的解决方案是可行的。

【讨论】:

  • 感谢您的回答。 @LastModifiedDate的定义和作用是什么?对于单元测试本身,我需要创建一个自定义Clock 用于审计配置,对吧?
  • @LastModifiedDate 将在更新实体时更新 (@PreUpdate),您也有 @CreatedDate,它与此类似,但仅在创建时发生 (@PrePersist)。对于测试,您可以提供您控制的固定时钟。但请注意,这是 a 建议,另一种选择可能是公开模拟 bean。
【解决方案2】:

有了 Mockito,你也许可以做这样的事情?

MyEntity sut = Mockito.spy(new MyEntity());

Mockito.doNothing().when(sut).initializeUUID();

但我不确定它在您的测试中的确切位置。


另外两个选项

  1. 模拟LocalDateTime.now(),让它返回你想要的值。但是可能persist过程中的其他代码调用了这个方法,可能不喜欢它。如果是这种情况,请转到其他选项
  2. 使用静态方法将LocalDateTime.now() 包装在您自己的类中,然后进行模拟。遗憾的是,涉及对实体类的微小更改,但只会模拟对 LocalDateTime.now() 的调用。

我没有描述在这种情况下如何使用 Mockito 进行模拟,因为我不熟悉它。我只用过 JMockit。但以上就是原则。

【讨论】:

  • 这是一个非常好的主意,但是 Mockito 间谍无法持久化:org.springframework.dao.InvalidDataAccessApiUsageException: Unknown entity: com.mycompant.entity.MyEntity$MockitoMock$1764544448;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
相关资源
最近更新 更多