Issue
I'm working on a Springboot application that's written in a mix of Java and Kotlin. We have rest controllers, services, and database repositories that all need to know the wall clock time.
We could call System.currentTimeMillis()
directly, but some of our unit tests need to mock the current time to test time-dependent behaviour, so we created this interface:
import java.time.OffsetDateTime;
public interface TimeService {
long currentTimeMillis();
OffsetDateTime nowUtc();
}
and this implementation of that interface:
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);
}
}
as well as this bean that tells Springboot how to create an instance of the interface:
@Bean
public TimeService timeService() {
return new SystemTimeService();
}
Now, our code can use Springboot to inject and use an instance of TimeService
like this:
@Service
class SomeService(
private val timeService: TimeService
) {
fun doSomethingWithTime() {
val now = timeService.currentTimeMillis()
// do something with now
}
}
The problem is that sometimes when running unit tests, timeService.currentTimeMillis()
returns 0L
, which should be impossible.
This only seems to happen when we run the entire test suite, and it happens less frequently when debugging, so I think it's a race condition, but I'm having a tricky time tracking it down. If I replace the uses of timeService.currentTimeMillis()
that are acting up with a direct call to System.currentTimeMillis()
, I can't reproduce the issue.
Is it possible that Springboot is mangling the lifecycle of this bean such that a mock is being re-used in a place where we don't intend it to be? Could there be some problem with the injected bean going out of scope because of the way that Kotlin handles threads and asynchronously executed suspended
functions? Have I found a bug in the JVM?
Any ideas or advice are appreciated.
Solution
I was able to add some logging that confirmed that my ApplicationContext contained a mock when I didn't expect it to. I found a test that uses @MockBean(TimeService::class)
to create the mock, but it wasn't cleaning up after itself. I added an @DirtiesContext
annotation to that test, and it seems to have solved the mock problem.
Answered By - MusikPolice
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.