提问者:小点点

如何伪造单元测试的最后修改时间?


我有一个简单的实体,要求上次修改时间应该在persist上更新。

@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();
    }
}

我需要实现一个作业,该作业可以查询超过某个时间(比如说一天)的实体,修改其状态并持久化它们。我在为包含这种用例的单元测试创建数据时遇到了问题。

尽管我手动设置了< code>lastModified时间,但无论设置值如何,< code>@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

我尝试过的事情:

>

  • 如何使用Mockito和jUnit模拟持久化和实体:模拟持久化实体不是一个办法,因为我需要将实体持久化在H2中以进行进一步检查。此外,我尝试使用这个技巧Spring Boot#7033使用间谍bean,结果相同。

    Hibernate提示:如何为所有实体激活实体侦听器:使用为单元测试范围配置的静态嵌套类< code>@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;
            }
        }
    

    脏方法:创建一个方法级类,用< code>@PrePersist扩展< code>MyEntity,该类“覆盖”了< code>lastModified值。它会产生< code > org . spring framework . Dao . invaliddataaccessapiusageexception 。为了解决这个问题,这样的实体依赖于< code>@Inheritance注释(JPA : Entity extend with entity),我不想仅仅为了单元测试而使用它。该实体不得在生产代码中扩展。


  • 共2个答案

    匿名用户

    您可以使用Spring Data JPA AuditingEntityListener。

    只需通过@org.springframework.data.jpa.repository.config.EnableJpaAudting启用它,并可选择提供自定义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提供一个< code>java.time.Clock,它已经解决了您关于测试的问题。但是您也可以提供一个专用的测试配置,指定一个不同的/模拟的< code>DateTimeProvider。

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

    匿名用户

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

    MyEntity sut = Mockito.spy(new MyEntity());
    
    Mockito.doNothing().when(sut).initializeUUID();
    

    但我不确定它到底适合你的测试。

    另外两个选择

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

    在这种情况下,我还没有描述如何与Mockito一起嘲笑,因为我不熟悉它。我只用过JMockit。但以上就是原则。