I am writing unit tests in kotlin, for this purpose I need to assign value on a "val", here is the simplified version of the code:
@Entity
@Table(name = "Request")
data class Request(
@Column(name = "Name")
val name: String,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null
@CreationTimestamp
@Column(name = "Created")
val created: LocalDateTime = LocalDateTime.now()
}
@Test
fun `test one`() {
val name = RandomStringUtils.randomNumeric(10)
val id = Random.nextLong(100)
val created = LocalDateTime.now().minusHours(48)
val request = Request(playerUid = playerUid).apply {
id = id
created = created
}
}
it has an compile error when assigning "created" in the test. How should I manage this unit test since I need to set my desire "created" value? (I can not touch any part of the "Request class")
CodePudding user response:
If you cannot change any part of the Request
class then you will not be able to change created
.
You will either need to test created
by using an approximate test range (created
needs to be 0<now<2s sort of thing)
It is a design flaw to encode a static accessor to functions like LocalDateTime.now()
- this should be set externally in a service class. If you really cannot do that, then here is another hacky approach:
- add a CLOCK object somewhere (does not need to be in a companion object) but ultimately you have to change the
created
assignment:
@Entity
@Table(name = "Request")
data class Request(
@Column(name = "Name")
val name: String,
) {
companion object {
/** used for carrying a Clock only in the case of tests **/
val CLOCK = ThreadLocal<Clock>()
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Long? = null
@CreationTimestamp
@Column(name = "Created")
val created: LocalDateTime = LocalDateTime.now(CLOCK.get() ?: Clock.systemUTC())
}
Normally you don't touch that CLOCK
but in the Unit Tests you define a
private val fixedClock = Clock.fixed(Instant.parse("2022-08-29T08:20:50Z"), ZoneOffset.UTC)
then you need
@BeforeEach
fun beforeEach() {
// this is necessary because the serialization of MemberMentorCommon.weeksOnPlan uses a clock
CLOCK.getOrSet { fixedClock }
}
@AfterEach
fun afterEach() {
CLOCK.remove()
}
Yes, ThreadLocals are nasty, but this allows you to change the behaviour of the Request class to override the now()
function