如何在 Spring 中抽象出 java.time.Clock 以用于测试目的

Posted

技术标签:

【中文标题】如何在 Spring 中抽象出 java.time.Clock 以用于测试目的【英文标题】:How to abstract away java.time.Clock for testing purposes in Spring 【发布时间】:2017-09-30 01:00:11 【问题描述】:

我有一个关于Time dependent unit tests的问题

假设我构建了包含服务接口及其实现的 Spring 应用程序

如果我想在测试中更改时钟,我将不得不“污染”生产代码并与例如setClock方法如下:

public interface MyService 
    void heavyBusinessLogic();
    void setClock(Clock clock);


@Service
public class MyServiceImpl implements MyService 

    private Clock clock = Clock.systemDefaultZone();

    @Override
    public void heavyBusinessLogic() 
        if (LocalDate.now(clock)...) 
            ... 
        
    

    @Override
    public void setClock(Clock clock) 
        this.clock = clock;
    
   

在测试中,我可以调用,例如:

service.setClock(Clock.fixed(Instant.parse("2017-02-14T01:22:00Z"), ZoneOffset.UTC));

如何在 Spring 中抽象出这种横切关注点?

我想坚持使用 java.time.Clock(我不想使用 Joda)

【问题讨论】:

为什么不使用构造函数注入呢?然后界面中没有setClock()方法 java.time.Clock 不是 bean ...这不是问题,因为您可以在生产配置中定义一个 Clock bean,并在您的测试配置中定义一个模拟... 【参考方案1】:

就个人而言,我只是在构造函数中添加时钟...

public MyServiceImpl(Clock clock) 
  this.clock = clock;

...也许添加一个不错的默认构造函数...

public MyServiceImpl() 
  this(Clock.systemDefaultZone());

这样您可以通过 spring 获取默认值并手动创建自定义时钟版本,例如在您的测试中。

当然,您也可以放弃默认构造函数,只需在生产配置中添加一个Clock bean,例如这样...

@Bean
public Clock clock() 
 return Clock.systemDefaultZone();

...它允许您在测试配置中使用模拟的 Clock 作为 bean,自动允许 Spring 通过构造函数注入 @Autowire 它。

【讨论】:

我刚刚遇到了另一个问题。我想在实体本身中使用时钟进行时间敏感比较,我认为注入时钟 bean 不是一个好主意。你有什么建议? 为什么你认为这不是一个好主意?我不太明白这有什么问题...... 它需要 aspectj 加载时间编织或 Hibernate LoadEventListener。我认为有一种更标准化的方式来做到这一点:)【参考方案2】:

如何建模时钟的好方法是使用ThreadLocal,尤其是当您在 JPA 实体中需要它时,例​​如:

@Entity
public class MyEntity 
   public static final ThreadLocal<Clock> CLOCK =
            ThreadLocal.withInitial(Clock::systemDefaultZone);

【讨论】:

IIRC,Java 8 日期时间类是线程安全的,因此可以使用简单的静态初始化持有者。

以上是关于如何在 Spring 中抽象出 java.time.Clock 以用于测试目的的主要内容,如果未能解决你的问题,请参考以下文章

最新的 Spring Data/Hibernate 在本机查询中不支持 java.time.LocalDate?

是否有任何选项可以在 Spring Boot 中使用 Jackson 为 java.time.* 包注册一次 Serializer/Deserializer?

java在抽象类中注入属性架构设计

如何在 Spring Boot 中注册自定义转换器?

教你如何在Spring Boot中使用RSocket

Spring Boot 问题使用 Jackson 序列化 java.time.LocalDateTime 以返回 ISO-8601 JSON 时间戳?