I want to know what is the best way to get an instance of ObjectMapper
from context settings in application.yml
without "overhead" configuration(which means, WebAppConfiguration
and ContextConfiguration
) in Spring Boot, with only the minimized number of classes to load in the tests.
I already know that, to load Jackson-related config in application.yml
(spring.jackson.xxx
), you must @Autowired
a Jackson2ObjectMapperBuilder
, and use builder.build()
to get an ObjectMapper
.
So I create a configuration class:
@Configuration
public class JacksonConfig {
@Autowired
private Jackson2ObjectMapperBuilder objectMapperBuilder;
@Bean
public ObjectMapper objectMapper() {
return objectMapperBuilder.build();
}
}
And a wrapper class:
@Service
@Slf4j
public class JsonConverter {
@Getter
@Autowired
private ObjectMapper mapper;
public String objectToJson(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("Cannot serialize object {} to JSON", obj);
throw new ServiceException(ErrorCode.JSON_CONVERTER_EXCEPTION)
.addLog("log", e.getMessage());
}
}
public Object jsonToObject(Class c, String json) {
try {
return mapper.readValue(json, c);
} catch (IOException e) {
log.error("Cannot deserialize JSON as instance of class {}", c.getName());
throw ServiceException.wrap(e, ErrorCode.JSON_CONVERTER_EXCEPTION)
.addLog("json", json)
.addLog("target class", c.getName());
}
}
public String messageErrorToJson(String message) {
Map result = Collections.singletonMap("responseError", message);
return objectToJson(result);
}
}
That works in gradle bootRun
, as well as in tests like this:
@Slf4j
@SpringBootTest // we can only launch the whole context to make Jackson2ObjectMapperBuilder to work
@WebAppConfiguration
@ContextConfiguration
class JsonConverterTest {
@Autowired
private JsonConverter jsonConverter;
...
I know it works, because when debugging it stops a breakpoint in JacksonAutoConfiguration
class.
But, I wonder if there is another simpler way to get it. Why I have to use @WebAppConfiguration
and @ContextConfiguration
? Why doesn't @SpringBootTest
load the application.yml
spring.jackson.xx
values?
Or, to make sure the application.yml
is loaded, how to limit the classes to load in a SpringBootTest
to minimize the number of classes to load, and speed up the tests?
EDIT:
If I delete @WebAppConfiguration
and ContextConfiguration
, and add @SpringBootTest(class = MyApplication.class)
, it also loads all the context/launch the whole app and works.
I'm looking for the same thing. However, for now, I use @JsonTest
on my unit test class to avoid full Spring initialization, and it works fine. I also found that the same effect can be achieved using combination @ExtendWith + @ContextConfiguration
or @ExtendWith + @Import
.
My code is as follows:
// This test class is under the same packages where @SpringBootApplication in src/main/java is written.
package com.example;
import com.example.security.user.profile.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
// This annotation comes from spring-boot-autoconfigure, please check if
// this dependency is already there, if you have spring-boot-starter-web
// or spring-boot-starter-test it is already there.
@JsonTest
// Or use the following two combinations instead of @JsonTest:
// @ExtendWith(SpringExtension.class)
// @Import(JacksonAutoConfiguration.class)
// Or use the following two combinations instead of @JsonTest:
// @ExtendWith(SpringExtension.class)
// @ContextConfiguration(classes = {JacksonAutoConfiguration.class})
@Log4j2
class MyUserProfileTestCase {
@Test
void serializationCase(@Autowired ObjectMapper objectMapper) {
var profile = new UserProfile();
profile.setEmail("ximinghui@example.com");
profile.setFullName(new PersonName("Xi", "Minghui"));
profile.setSex(Sex.MALE);
assertDoesNotThrow(() -> log.info(objectMapper.writeValueAsString(profile)));
}
}
I'm not sure about their principles either.