Issue
I have an attribute in a DTO and Entity defined like this:
val startDate: OffsetDateTime,
The dto has a toEntity
method:
data class SomeDTO(
val id: Long? = null,
val startDate: OffsetDateTime,
) {
fun toEntity(): SomeEntity {
return SomeEntity(
id = id,
startDate = startDate,
)
}
}
And a controller
@RestController
@RequestMapping("/some/api")
class SomeController(
private val someService: SomeService,
) {
@PostMapping("/new")
@ResponseStatus(HttpStatus.CREATED)
suspend fun create(@RequestBody dto: SomeDTO): SomeEntity {
return someService.save(dto.toEntity())
}
}
And I have a failing test:
@Test
fun `create Ok`() {
val expectedId = 123L
val zoneId = ZoneId.of("Europe/Berlin")
val dto = SomeDTO(
id = null,
startDate = LocalDate.of(2021, 4, 23)
.atStartOfDay(zoneId).toOffsetDateTime(),
)
val expectedToStore = dto.toEntity()
val stored = expectedToStore.copy(id = expectedId)
coEvery { someService.save(any()) } returns stored
client
.post()
.uri("/some/api/new")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(dto)
.exchange()
.expectStatus().isCreated
.expectBody()
.jsonPath("$.id").isEqualTo(expectedId)
coVerify {
someService.save(expectedToStore)
}
}
The test fails for the coVerify
because the startDate
does not match:
Verification failed: ...
... arguments are not matching:
[0]: argument: SomeEntity(id=null, startDate=2021-04-22T22:00Z),
matcher: eq(SomeEntity(id=null, startDate=2021-04-23T00:00+02:00)),
result: -
Semantically, the startDate
s match, but the timezone is different. I wonder how I can enforce coVerify
to either use a proper semantic comparison for type OffsetDateTime
or how I can enforce the internal format of OffsetDateTime
=? Or what other approach should we use to verify the expectedToStore
value is passed to someService.save(...)
?
I could use withArgs
but it is cumbersome:
coVerify {
someService.save(withArg {
assertThat(it.startDate).isEqualTo(expectedToStore.startDate)
// other manual asserts
})
}
Solution
tl;dr
Add this to your application.properties
:
spring.jackson.deserialization.adjust-dates-to-context-time-zone=false
This way, the offset will be deserialized as retrieved and not altered.
I created a (slightly modified) reproduction repository on GitHub. Inside the Controller the value of dto.startDate
is already 2021-04-22T22:00Z
, thus at UTC.
By default, the serialization library used "Jackson" aligns all offsets during deserialization to the same configured offset.
The default offset used is +00:00
or Z
, which resembles UTC.
You can enable / disable this behaviour over the property spring.jackson.deserialization.adjust-dates-to-context-time-zone={true false}
and set the timezone with spring.jackson.time-zone=<timezone>
Alternatively, you can force to align the offset with an other timezone during deserialization:
spring.jackson.time-zone=Europe/Berlin
This way, the offset will be the aligned with the timezone Europe/Berlin
.
Answered By - Endzeit