2

I'm trying to verify that my reactive rest controller transfers the correct data. This data contains a ZonedDateTime field I need to retain. However, when querying the rest controller with a WebTestClient, my verification fails because the received time is suddenly in UTC.

@Data
public class SimpleData {
    ZonedDateTime zdt;
}

@RestController
class SimpleDataController {
    @Autowired SimpleDataService service;
    @GetMapping("/simple")
    List<SimpleData> getData() {
        return service.getTimes();
    }
}

@Service
class SimpleDataService {
    public static final SimpleData DATA = new SimpleData();
    static {
        DATA.setZdt(ZonedDateTime.now(ZoneId.of("Europe/Berlin")));
    }

    public List<SimpleData> getTimes() {
        return List.of(DATA);
    }
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ActiveProfiles("test")
class ApplicationTests {
    @Test
    void simpleDataTest() {
        List<SimpleData> fromRest = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
             .get().uri("/simple").exchange()
             .expectBodyList(SimpleData.class)
             .returnResult().getResponseBody();
        assertThat(fromRest).containsAll(Collections.singletonList(SimpleDataService.DATA));
    }
}

This fails with

Expecting ArrayList: <[SimpleData(zdt=2020-08-05T09:30:40.291415300Z[UTC])]> to contain: <[SimpleData(zdt=2020-08-05T11:30:40.291415300+02:00[Europe/Berlin])]> but could not find the following element(s): <[SimpleData(zdt=2020-08-05T11:30:40.291415300+02:00[Europe/Berlin])]>

The time itself is correct - the time zone difference is substracted from the hour field - but it fails the equals obviously. Funnily enough, if you query the url with a client, the JSON contains the correct data:

[{"zdt":"2020-08-05T11:44:10.4740259+02:00"}]

It seems to be the TestWebClient converting the time.

Is this intended? Can I change this behaviour somehow?

daniu
  • 14,137
  • 4
  • 32
  • 53
  • 1
    Will this help https://stackoverflow.com/questions/59097664/timezone-of-zoneddatetime-changed-to-utc-during-auto-conversion-of-requestbody-w? – Kavithakaran Kanapathippillai Aug 05 '20 at 10:13
  • Does this answer your question? [Timezone of ZonedDateTime changed to UTC during auto conversion of RequestBody with Spring Boot](https://stackoverflow.com/questions/59097664/timezone-of-zoneddatetime-changed-to-utc-during-auto-conversion-of-requestbody-w) – Thirumalai Parthasarathi Aug 05 '20 at 10:18
  • @KavithakaranKanapathippillai Hmm it looks like the same thing, but setting the value as in the accepted answer doesn't change the behavior for me – daniu Aug 05 '20 at 10:30
  • Just realised it won't work as `WebTestClient` is a manually created bean so it wouldn't know about spring properties – Kavithakaran Kanapathippillai Aug 05 '20 at 10:34

2 Answers2

1

In summary, ADJUST_DATES_TO_CONTEXT_TIME_ZONE alone is not sufficient, WebTestClient should not be created manually and instead it should use @AutoConfigureWebTestClient and autowire the client.

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
    @ActiveProfiles("test")
    @AutoConfigureWebTestClient
    class ApplicationTests {

        @Autowired
        private WebTestClient client;
    
        @Test
        void simpleDataTest() {
          List<SimpleData> fromRest = client.
             .get().uri("/simple").exchange()
             .expectBodyList(SimpleData.class)
             .returnResult().getResponseBody();
          assertThat(fromRest)
              .containsAll(singletonList(SimpleDataService.DATA));
       }
    }

application.properties

    spring.jackson.deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE = false
  • Sorry, I wasn't using the autowired client initially in my answer and updated it.It also needs `spring-boot-starter-webflux` dependency and I assume you have it – Kavithakaran Kanapathippillai Aug 05 '20 at 10:53
  • OMG now it contains the correct zoned time, but is missing the zone id (Europe/Berlin), so it still fails. Anyway, that was it, thank you. – daniu Aug 05 '20 at 10:59
1

I couldn't sleep without getting a solution for this. So here you go! A working solution to your problem. :)

@JsonComponent
class ZonedDateTimeJsonSerializer extends JsonSerializer<ZonedDateTime> {
    static DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
    
    @Override
    public void serialize(ZonedDateTime zdt, JsonGenerator jsonGenerator, 
      SerializerProvider serializerProvider) throws IOException, 
      JsonProcessingException {
        jsonGenerator.writeString(zdt.format(formatter));
    }
}

@JsonComponent
class ZonedDateTimeJsonDeserializer extends JsonDeserializer<ZonedDateTime> {
    static DateTimeFormatter formatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;

    @Override
    public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return ZonedDateTime.parse(p.getValueAsString(),formatter);
    }
}

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ActiveProfiles("test")
@AutoConfigureWebTestClient
class ApplicationTests {

    @Autowired
    private WebTestClient client; // important! as the @jsonComponent has to be picked up

    @Test
    void simpleDataTest() {
      List<SimpleData> fromRest = client.
         .get().uri("/simple").exchange()
         .expectBodyList(SimpleData.class)
         .returnResult().getResponseBody();
      assertThat(fromRest)
          .containsAll(singletonList(SimpleDataService.DATA));
   }
}
Thirumalai Parthasarathi
  • 4,541
  • 1
  • 25
  • 43