Spring Boot version: 2.5.14
We have value properties like this:
@Value("${app.timeout}")
private Duration timeout;
It is working in normal application run, but it's not working in unit test:
nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.time.Duration'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.time.Duration': no matching editors or conversion strategy found
This is the structure of my unit test
@SpringJUnitConfig({Service.class, Config.class})
@TestPropertySource(properties = {"app.timeout=5s"})
class UnitTest {
// unit tests here
}
Comment From: wilkinsona
Conversion of a String to a Duration is a Spring Boot feature supported by its ApplicationConversionService. You plain unit test doesn't appear to be using any Spring Boot features so this conversion isn't possible. If you use @SpringBootTest it should work. If you have any further questions, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.
Please note that Spring Boot 2.5.x is no longer supported. You should upgrade to 2.7.x or later as soon as possible.
Comment From: andreas-ajaib
So do you say Spring won't be supporting this in plain unit tests? I see we already have @TestPropertySource that supports the property injection. So, I expect that Duration-typed property will also be working. Using @SpringBootTest is too much.
Comment From: philwebb
There is an open Spring Framework issue to add support, but currently you need to hook in Spring Boot's org.springframework.boot.convert.ApplicationConversionService.ApplicationConversionService.
If you don't want to do that using @SpringBootTest you might be able to use the initializers attribute of @SpringJUnitConfig and create an initializer that effectively does this.
Comment From: linarkou
@philwebb thanks for your tip about initializers, it made the trick and I successfully ended up with this (tested on Spring Boot 2.7.10 & junit5)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(initializers = DurationPropertyTest.ConversionInitializer.class)
@TestPropertySource(properties = {"app.timeout=10s"})
class DurationPropertyTest {
static class ConversionInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().setConversionService(new ApplicationConversionService());
}
}
@Value("${app.timeout}")
private Duration timeout;
@Test
void testDurationConversion() {
assertEquals(Duration.ofSeconds(10L), timeout);
}
}
And one more way to solve the problem is just to load ConversionService bean into the test context
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DurationPropertyTest.ConversionInitializer.class)
@TestPropertySource(properties = {"app.timeout=10s"})
class DurationPropertyTest {
static class ConversionInitializer {
@Bean
public ConversionService conversionService() {
return new ApplicationConversionService();
}
}
@Value("${app.timeout}")
private Duration timeout;
@Test
void testDurationConversion() {
assertEquals(Duration.ofSeconds(10L), timeout);
}
}