我正在开发一个由Java和Kotlin混合编写的Springboot应用程序。我们有休息控制器、服务和数据库存储库,它们都需要知道墙上的时钟时间。
我们可以直接调用System.currentTimeMillis()
,但我们的一些单元测试需要模拟当前时间来测试与时间相关的行为,所以我们创建了这个接口:
import java.time.OffsetDateTime;
public interface TimeService {
long currentTimeMillis();
OffsetDateTime nowUtc();
}
这个接口的实现:
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
public class SystemTimeService implements TimeService {
@Override
public long currentTimeMillis() {
return System.currentTimeMillis();
}
@Override
public OffsetDateTime nowUtc() {
return OffsetDateTime.now(ZoneOffset.UTC);
}
}
以及这个bean,它告诉Springboot如何创建接口的实例:
@Bean
public TimeService timeService() {
return new SystemTimeService();
}
现在,我们的代码可以使用Springboot注入并使用TimeService
的实例,如下所示:
@Service
class SomeService(
private val timeService: TimeService
) {
fun doSomethingWithTime() {
val now = timeService.currentTimeMillis()
// do something with now
}
}
问题是,有时在运行单元测试时,timeService.currentTimeMillis()
会返回0L
,这应该是不可能的。
这似乎只在我们运行整个测试套件时发生,而在调试时发生的频率较低,所以我认为这是一个竞争条件,但我很难追踪它。如果我用对System.currentTimeMillis()
的直接调用来代替对timeService.currentTimeMillis()
的使用,我就无法重现这个问题。
Springboot是否有可能破坏了这个bean的生命周期,以至于mock在我们不打算使用的地方被重新使用?注入的bean会因为Kotlin处理线程和异步执行suspended
函数的方式而超出作用域吗?我在JVM中发现错误了吗?
任何想法或建议都将不胜感激。
我能够添加一些日志,在我没想到的时候确认我的ApplicationContext包含一个mock。我发现了一个使用@MockBean(TimeService::class)
创建mock的测试,但它本身并没有清理。我在该测试中添加了一个@DirtiesContext
注释,它似乎解决了mock问题。