How to simulate a CDI #Produces annotation on a JUnit - junit

I am writing a JUnit test for a class which does something like:
org.glassfish.jersey.client.JerseyClient client = getHTTPClient(SSLContextFactory.getContext(), connectTimeout, readTimeout, true);
client.register(CustomJAXBContextProvider.class); // subclass of javax.ws.rs.ext.ContextResolver<JAXBContext>
client.property(MarshallerProperties.JSON_INCLUDE_ROOT, true);
WebTarget webTarget = client.target(contextPath);
Response response = webTarget.request(MediaType.APPLICATION_JSON.get()
return response.readEntity(ResponseModel.class);
The application runs inside a WebLogic container and has another class with a CDI #Produces annotation:
public class ObjectMapperProvider {
private ObjectMapper objectMapper;
#Produces
public ObjectMapper objectMapper() {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JSR310Module());
objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
return objectMapper;
}
}
When I run the JUnit test from outside WebLogic I get an error
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "fieldName" (class ResponseModel), not marked as
ignorable
Because the JSON response contains a field which is not declared in the model and the JUnit is not obtaining the ObjectMapper through the #Produces annotation but getting a default one. The JAXBContext is EclipseLink MOXy.
My question is: How do I get the code tested by my JUint to instantiate ObjectMapper as returned from ObjectMapperProvider instead of a default one lacking the DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false?

We cover this exact scenario using Mockito. Depending on how JaxBContext is created, you could use a spy to return a mock. Without posting your complete test code and the class under test, it's hard to give a more complete answer than that.

Related

How to specify date format in JSON response when running a unit test with MockitoJUnitRunner

I have a spring boot application that uses Swagger 2.0 to generate API and model objects from Swagger API definitions.
I am running some of the unit tests using MockitoJUnitRunner.
One of the API returns an object containing a date field.
#JsonProperty("departureDate")
private LocalDate departureDate = null;
I am having some problems in trying to get expected date format in JSON response in the unit test.
My test looks something like this.
#RunWith(MockitoJUnitRunner.class)
public class TourApiControllerStandaloneTest {
//mock declarations
...
private JacksonTester<TourHeader> jsonTourHeader;
// controller under test
private TourApiController tourApiController;
private TourleaderMockData tourleaderMockData;
...
#Before
public void setup() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new LocalDateModule()); //LocalDateModule formats the date in yyyy-mm-dd format
tourApiController = new TourApiController(objectMapper,
httpServletRequestMock, tourServiceMock);
JacksonTester.initFields(this, objectMapper);
mockMvc = MockMvcBuilders.standaloneSetup(tourApiController)
.setControllerAdvice(new GlobalExceptionHandler())
.build();
tourleaderMockData = new TourleaderMockData();
}
#Test
public void getTourHeaderWhenExists() throws Exception {
...
// given
given(tourServiceMock.getTourHeader(tourNumber))
.willReturn(tourleaderMockData.getMockTourHeaderRecord(tourNumber));
// when
MockHttpServletResponse response = mockMvc.perform(
get("/tour/" + tourNumber + "/header").accept(MediaType.ALL))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
// *************** THIS ASSERTION IS FAILING ****************
assertThat(response.getContentAsString()).isEqualTo(
jsonTourHeader.write(tourleaderMockData.getMockTourHeaderRecord(tourNumber)).getJson()
);
}
}
Date format returned in JSON response from API call and serlized JSON from mock header object do not match. This is how the date appears in actual and expected response.
** Actual **
{ ...,"departureDate":[2018,12,1], ...}
** Expected **
{...,"departureDate": "2018-12-01", ...}
The same assertion works i.e. date format is coming as expected when I am using SpringRunner as below.
#RunWith(SpringRunner.class)
#WebMvcTest(TourApiController.class)
Can someone please advise what I can do to get expected date format when I am running the test with MockitoJUnitRunner. Thanks.
Ok, I got the answer from another thread on stackoverflow
https://stackoverflow.com/a/35323312/5519519
So basically I needed to create an instance of MappingJackson2HttpMessageConverter and set my object mapper into this instance.
And then pass this message converter at the time of setting up mockMvc.
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new
MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
mockMvc = MockMvcBuilders.standaloneSetup(tourApiController)
.setControllerAdvice(new GlobalExceptionHandler())
.setMessageConverters(mappingJackson2HttpMessageConverter)
.build();
This resolved the issue.

jackson-dataformat-xml turns #ResponseBody to XML

So I had a perfectly working Spring app. Most of my controller methods are for ajax calls that return JSON via #ResponseBody with the jackson api and returns my Java POJO to JSON.
I have a need to turn XML to JSON, so I find that Jackson has a tool for that, and I add this to my POM to use the library:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
So that I may use this:
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(sb.toString().getBytes());
But now the #ResponseBody is returning XML and not JSON. I Remove the dependency and the controllers return JSON again.
Any way to get both? I want the xmlMapper, and JSON from the response body.
jackson-dataformat-xml appears to be registering a MappingJackson2HttpMessageConverter with a XmlMapper, along with other HttpMessageConverters that work with XML. If you always intended to return JSON from your controllers, you can change what HttpMessageConverter your app uses by overriding configureMessageConverters
For Spring 5.0 and above,
#Configuration
public class HttpResponseConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> supportsXml(converter) || hasXmlMapper(converter));
}
private boolean supportsXml(HttpMessageConverter<?> converter) {
return converter.getSupportedMediaTypes().stream()
.map(MimeType::getSubtype)
.anyMatch(subType -> subType.equalsIgnoreCase("xml"));
}
private boolean hasXmlMapper(HttpMessageConverter<?> converter) {
return converter instanceof MappingJackson2HttpMessageConverter
&& ((MappingJackson2HttpMessageConverter)converter).getObjectMapper().getClass().equals(XmlMapper.class);
}
}
For older versions of Spring, replace implements WebMvcConfigurer with extends WebMvcConfigurerAdapter
Add Accept: application/json to HTTP request header.
Read this for an analysis of how Spring does content negotiation and allows producing either XML or JSON.
The simplest way is to add an extension at the URL: Instead of /path/resource use /path/resource.json
You may also add a format parameter e.g. /path/resource?format=json or pass an appropriate Accept header
In my case, the XmlMapper was actually inserted into the application context as an #Bean. The other solutions here did not work for me. It seems like one of those issues where context matters, so for people coming here from a different context than the other answerers, here's another angle: I had to insert my own ObjectMapper.
#Configuration
public class XmlMapperConfiguration {
#Bean // me, culprit
public XmlMapper xmlMapper() {
return new XmlMapper();
}
#Bean // to make sure the rest of the application still works with JSON
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
I'm going to throw in an #Primary on the ObjectMapper one. It seems suspicious that Spring would even choose that one consistently. Since XmlMapper extends ObjectMapper, why would it not take that one, so #Primary won't hurt.

Use two differently configured ObjectMappers in one Spring Boot application

I am working on a middleware-app which deserializes values received via RestTemplate as json-String from a legacy-API (so, no influence on "their" data model and thus needing some custom config for my objectmapper consuming this api), and the app itself serves a restful API with (partially enriched and composited) data based on the legacydata as json, too.
Now, my legacy-Mapping-Classes' Constructors are all sharing a common structure like this at the moment:
...
private ObjectMapper mapper;
public MyMapper() {
this.mapper = new ObjectMapper();
this.mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
...
because I use Jackson to de-serialize the json from the legacysystem. Basically I want to refactor this redundance using Springs DI Container.
So I tried to create my own Objectmapper #Component which just extends ObjectMapper like it is stated in some answers in this thread:
Configuring ObjectMapper in Spring - lets call it FromLegacyObjectMapper - instead of initializing my mapper in every class, so I created one and used
#Autowired
private FromLegacyObjectMapper
(or the constructorinjection-equivalent, but for simplicitys sake..).
But this had some serious sideeffects. Actually, I wasn't able to deserialize clientjson to viewmodels in my controllers anymore because of the rootvalue-wrapping, because the autowiring overwrites the spring boot standard objectmapper which I actually need when deserializing viewModels from my frontend.
I try to get it up and running like this:
frontend <---> My Api using Standard ObjectMapper <--> viewModel created by consuming legacy-Api-json using FromLegacyObjectMapper
So, what I surely could do is using a baseclass for my mappingclasses and just add the code above to the base constructor, and let every Mapperclass extend this base, but actually I hoped to find a way to use springs dependency injection container instead. I am out of ideas for now, so I hope anyone could help me out!
edit: To make it perhaps a bit clearer please see Moritz' answer below and our discussion in the comments. I am well aware I am able to use #Qualifier annotation, but this would just solve the problem if there is a way to add the #Qualifier to the standard objectmapper used in spring controllers. I'll do some research myself, but other answers are highly welcome.
I would try adding two different ObjectMappers to the Spring container. You could add something like this, for example to your Application class (assuming that is the one annotated with #SpringBootApplication):
#Bean
#Qualifier("fromLegacy")
public ObjectMapper fromLegacyObjectMapper() {
// create and customize your "from legacy" ObjectMapper here
return objectMapper;
}
#Bean
#Qualifier("default")
public ObjectMapper defaultObjectMapper() {
// create your default ObjectMapper here
return objectMapper;
}
Then you can inject the "from legacy" ObjectMapper in classes that use the legacy API like this:
public class SomeServiceUsingLegacyApi {
private final ObjectMapper objectMapper;
#Autowired
public SomeServiceUsingLegacyApi(#Qualifier("fromLegacy") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// [...]
}
And in other classes, using the other API accordingly:
public class SomeServiceUsingOtherApi {
private final ObjectMapper objectMapper;
#Autowired
public SomeServiceUsingOtherApi(#Qualifier("default") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// [...]
}

Jackson serialization and lazy loading in Spring MVC

I'm trying to serialize an object with several lazily loaded properties, and I'm getting the following error:
Could not write content: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
After some searching I have tried this in my #Configuration class, but it doesn't seem to help:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
Hibernate4Module hibernateModule = new Hibernate4Module();
hibernateModule.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
objectMapper.registerModule(hibernateModule);
return objectMapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper());
return converter;
}
When I change to FetchType.EAGER the application works fine.
The problem is related to the fact that related objects to the main one are not really loaded when you execute the query and not use the FetchType.EAGER; by using the FetchType.EAGER you tell hibernate: load the main entity and all the related entities; this can have not too much sense (you can risk to load all the database in one query)
Returning to jackson marshaller, when objects are "proxied" it is not able in serializing them
IMHO i'd do the following:
i'd create a DTO object to be serialized (I'd not serialize the Hibernate object)
i'd put in this DTO only the needed properties and data
if user need to see related objects a specific call would be executed and data related to the selected object will be loaded from DB and serialized (in specific DTO objects)

How to serialize boolean to JSON as strings using Jackson

We have developed a REST service using Jersey JAX-RS and Jackson (version 2.1.5) for JSON serialization.
As the application is supposed to be a drop-in replacement for the older legacy service acting as a backend to an existing mobile app, we need to do some tweaking to the way Jackson serializes boolean values.
Existing mobile app expects boolean values to be expressed as strings of "true" and "false" like this:
{"Foo":"true","Bar":"false"}
So I have searched for a way to influence the Jackson serialization to output booleans as strings, but I have no success.
Oh, and btw - since our model classes have been generated from xml schemas using JAXB class generation, we can not annotate the classes with json annotations.
I have tried to register a module with ObjectMapper, that provides a customized serializer for boolean objects, but it did not seem to work.
Jackson 2.16 Custom Serializer for primitive data type .
you should write your own serializer. example code for boolean data type
// create a module with a custom Boolean serializer
class BooleanSerializer extends JsonSerializer<Boolean> {
private final static Logger logger = LoggerFactory.getLogger(BooleanSerializer.class);
#Override
public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
logger.info("serializing boolean value as a Strng {}",value);
jgen.writeString(value.toString());
}
}
//register custom BooleanSerializer class with ObjectMapper.
// Here's where we configure the object mapper
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("BooleanAsString", new Version(1, 0, 0, null, null, null));
simpleModule.addSerializer(Boolean.class,new BooleanSerializer());
simpleModule.addSerializer(boolean.class,new BooleanSerializer());
mapper.registerModule(module);
Okay, it seems that either my IDE or Maven was acting up and refused to build or reference the changes I made in my ObjectMapper configuration.
For the sake of the future visitors, here is the gist of the solution to the issue of making Jackson data binding to spit out boolean values as strings:
In my customized ObjectMapper context resolver, I just had to add special serializer for boolean object types:
// create a module with a custom Boolean serializer
SimpleModule module = new SimpleModule("BooleanAsString", new Version(1, 0, 0, null, null, null));
module.addSerializer(new NonTypedScalarSerializerBase<Boolean>(Boolean.class){
#Override
public void serialize(Boolean value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
ObjectMapperProvider.log.debug("serializing boolean value as a Strng");
jgen.writeString(value.toString());
}
});
// Here's where we configure the object mapper
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
That's it. If you know how to configure your ObjectMapper, then this should be enough to get you going.
Since version 2.8, just make this:
ObjectMapper mapper = new ObjectMapper()
mapper.configOverride(Boolean.class).setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING))
mapper.configOverride(boolean.class).setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING))