如何在 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?
Spring Boot 问题使用 Jackson 序列化 java.time.LocalDateTime 以返回 ISO-8601 JSON 时间戳?