Springboot and Axon: Basically I am unit testing an aggregate that uses three different ObjectMapper instances, each with a different configuration. These are defined in config class :
#Configuration
public class JacksonConfiguration {
#Bean(name="flatMapper")
#Primary
public ObjectMapper flatMapper(){
return new ObjectMapper();
}
#Bean(name="unknownPropertiesMapper")
public ObjectMapper unknownPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
#Bean(name="nullPropertiesMapper")
public ObjectMapper nullPropertiesMapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}
They are injected and used in my aggregate as follow:
#Aggregate
#Data
#Component
public class MyAggregate {
#Autowired
#Qualifier("flatMapper")
private ObjectMapper flatMapper;
#Autowired
#Qualifier("unknownPropertiesMapper")
private ObjectMapper unknownPropertiesMapper;
#Autowired
#Qualifier("nullPropertiesMapper")
private ObjectMapper nullPropertiesMapper;
#AggregateIdentifier
private String id;
//Methods and Handlers: a method is using "unknownPropertiesMapper" is "changedKeySet"
when I run SpringBootApplication everything is properly instanciated and working as expected, but when testing I get NullPointerException over thier instances:
#ContextConfiguration(classes = JacksonConfiguration.class)
#SpringBootTest(classes = OccurrenceAggregate.class)
#ComponentScan(basePackageClasses = MyAggregate.class)
public class AggregateTest {
private FixtureConfiguration<MyAggregate> fixture;
#BeforeEach
public void setUp() {
fixture = new AggregateTestFixture<>(MyAggregate.class);
}
#Test
public void myTest() {
fixture.givenNoPriorActivity()....
}
test console:
org.axonframework.test.AxonAssertionError: The command handler threw an unexpected exception
Expected <ANYTHING> but got <exception of type [NullPointerException]>. Stack trace follows:
java.lang.NullPointerException
at com.example.business.aggregates.MyAggregate.changedKeySet(MyAggregate.java:185)
changedKeySet() is throwing NPE because its using unknownPropertiesMapper and it is value is null.
as I mentionned it works fine when I run the Main class but not in tests (Junit5).
The Aggregate is not set up correctly. The correct way to inject a Spring Bean into the Aggregate is to add it to the CommandHandler method.
#CommandHandler
public void handle(ACommand cmd, ObjectMapper flatMapper) {
In the test fixture you can inject it this way:
fixture.registerInjectableResource(flatMapper);
Related
I have tried with #Autowired on the objectMapper, also tried to Mock it but no success, I just want to use the writeValueAsStringMethod so I do not have to pass a long json string to the content method below.
If I mark my class with #SpringBootTest and also #AutoconfigureMockMvc it works (the objectmapper is not null) but I believe that there must be another way so that it does not become mandatory to use this annotations.
TestClass:
#ExtendWith(MockitoExtension.class)
public class CarControllerTest {
private MockMvc mockMvc;
#InjectMocks
private CarController carController;
#Mock
private ObjectMapper objectMapper;
#MockBean
private CarParts carParts;
#BeforeEach
public void before() {
mockMvc = MockMvcBuilders.standaloneSetup(carController).build();
}
#Test
#DisplayName("Car Controller Test")
public void carControllerTest() {
try {
CarCustomRequest carCustomRequest = buildRequest();
ResultActions resultActions = mockMvc.perform(post("/custom/endpoint")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(carCustomRequest)));
MvcResult mvcResult = resultActions.andExpect(status().isOk()).andReturn();
assertTrue(mvcResult.getResponse().getStatus() == 200);
} catch (Exception e) {
fail("Error testing /custom/endpoint");
}
}
In order to #Autowire you need to load component classes that create the corresponding bean. To make tests faster you could define custom application context and load required beans only inter of using #SpringBootTest without params.
#SpringBootTest(classes = JacksonAutoConfiguration.class)
class ObjectMapperTest {
#Autowired
private ObjectMapper mapper;
#Test
void checkObjectMapper() {
assertNotNull(mapper);
}
}
I would not use #Mock in this case because it will be required to create stubs for required methods.
As an alternative, you could simply create a new instance of the ObjectMapper in test.
class ObjectMapperTest {
private ObjectMapper mapper = new ObjectMapper();
#Test
void checkObjectMapper() {
assertNotNull(mapper);
}
}
You could also register additional modules if required
objectMapper.registerModule(new JavaTimeModule());
I am writing a simple test for a controller endpoint.
It works fine when I do the following.
#SpringBootTest
#ContextConfiguration(classes = {
HomeController.class,
HomeControllerTest.class
})
class HomeControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private static final String URL = "/a";
private static final ObjectMapper objectMapper = new ObjectMapper();
#Test
public void test() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
Request request = new Request();
mockMvc.perform(post(URL)
.contentType("application/json")
.content(objectMapper.writeValueAsString(request))
.andExpect(status().isOk());
}
}
But I do not want to have to create the mockMvc and concern with webApplicationContext.
Thus instead, attempting to use #AutoConfigureMockMvc instead as follows.
But this doesn't work. Fails with following error.
java.lang.AssertionError: Status expected:<200> but was:<403> Expected
:200 Actual :403
What am I doing wrong?
My attempt which throws above error.
#SpringBootTest
#AutoConfigureMockMvc // using this annotation instead
#ContextConfiguration(classes = {
HomeController.class,
HomeControllerTest.class
})
class HomeControllerTest {
// wiring mockMvc instead
// no webApplicationContext autowired
#Autowired
private MockMvc mockMvc;
private static final String URL = "/a";
private static final ObjectMapper objectMapper = new ObjectMapper();
#Test
public void test() throws Exception {
Request request = new Request();
mockMvc.perform(post(URL)
.contentType("application/json")
.content(objectMapper.writeValueAsString(request))
.andExpect(status().isOk());
}
}
Try create test like this exemple it is better.
#SpringBootTest
#AutoConfigureMockMvc
public class HomeControllerTest
private ObjectMapper mapper;
private MyControler myController;
private ServiceInSideConttroler service;
add your atributes
#Before
public init(){
this.mapper = new ObjectMapperConfiguration().mapper();
this.service = mock(ServiceInSideConttroler.class);
this.myController = new MyController(service);
}
#Test
public void test() throws Exception {
// exemple how mock reponse from any service or repository.
when(service.findById(any(Long.class)))
.thenReturn(Optional.of(budget));
Object resp = myController.save(mockben());
......
aserts(resp)
}
}
Remember, to work with the tests like this, do not use #Authwired in the attributes, only in the constructor of the classes annotated with #service, #componet, #controller etc .... that is, any class controlled by Spring that will use dependecia injection. Also rember asserts your reponse.
I have a microservice application which has a REST controller and uses an ObjectMapper - trip-definition-service.
#SpringBootApplication
public class TripDefintionService extends TripCommonApplication {
public static void main(String[] args) {
SpringApplication.run(args, TripDefinitionService.class);
}
Now I want another microservice application to create a bean for that same ObjectMapper - trip-common-service.
#SpringBootApplication
public abstract class TripCommonApplication { }
As of now I have build trip-common-service and have kept a jar as a dependency on trip-definition-service.
Now I have a configuration created on trip-common for ObjectMapper as follows -
#Configuration
#Qualifier("standardObjectMapper")
public ObjectMapper getMapper() {
return new ObjectMapper();
}
And I am using a the ObjectMapper which was created in trip-common in my trip-definition-service.
#Service
public class Service {
#Autowired
#Qualifier("standardObjectMapper")
private ObjectMapper objectMapper;
public void doSomething() {
objectMapper.readValue(......);
}
Now when I start my trip-definition-service, it says its not able to find an ObjectMapper bean of type standardObjectMapper.
Need some help/suggestions on this and how to get this done.
I have a Spring Boot web app with several #RestController classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization ), I have created a custom ObjectMapper:
#Configuration
public class Config{
#Bean
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper:
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper bean for its own internal workings?
The Simone Pontiggia answer is in the correct direction. You should create one #Primary bean, which Spring will use in its internals, and then to create your own ObjectMapper beans and autowired them using #Qualifier.
The problem here is that, creating default bean like:
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Won't actually work as expected, because the Spring default ObjectMapper has additional configurations.
The correct way to create default ObjectMapper that will be used by spring, is:
#Bean
#Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
You can find more information about the Spring default ObjectMapper here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper
Since I didn't want to touch Spring's default ObjectMapper, creating a #Primary ObjectMapper to shadow Spring's default ObjectMapper was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor which registers in Spring's context a custom, non primary ObjectMapper:
#Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
.getBeanDefinition();
// Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
// as primary
beanDefinition.setPrimary(false);
final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
beanDefinition.addQualifier(mapperQualifier);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
}
private ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper bean.
My qualifier is an annotation which is annotated with #Qualifier:
#Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper using my custom annotation, like this:
#Repository
public class MyDao {
#Autowired
public MyDao(DataSource dataSource, #PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}
You can provide a standard ObjectMapper and your customized object mapper, and set the standard as #Primary.
Then gives your custom ObjectMapper a name and use it with #Qualifier annotation.
#Configuration
public class Config{
//This bean will be selected for rest
#Bean
#Primary
public ObjectMapper stdMapper(){
return new ObjectMapper();
}
//You can explicitly refer this bean later
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
Now you can reference your custom mapper
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
#Qualifier("customObjectMapper")
ObjectMapper objectMapper
//Dao implementation...
}
#Resource("custonmObjectMapper") will do the same of #Autowired and #Qualifier together
You can create:
public class MapperUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(byteArrray, parseType);
}
}
ObjectMapper is thread-safe. However, some people discourage having single instance because of performance issues (Should I declare Jackson's ObjectMapper as a static field? ).
I try to write junit test to my service.
I use in my project spring-boot 1.5.1. Everything works fine, but when I try to autowire bean (created in AppConfig.class), it gives me NullPointerException. I've tried almost everything.
This is my configuration class:
#Configuration
public class AppConfig {
#Bean
public DozerBeanMapper mapper(){
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.setCustomFieldMapper(new CustomMapper());
return mapper;
}
}
and my test class:
#SpringBootTest
public class LottoClientServiceImplTest {
#Mock
SoapServiceBindingStub soapServiceBindingStub;
#Mock
LottoClient lottoClient;
#InjectMocks
LottoClientServiceImpl lottoClientService;
#Autowired
DozerBeanMapper mapper;
#Before
public void setUp() throws Exception {
initMocks(this);
when(lottoClient.soapService()).thenReturn(soapServiceBindingStub);
}
#Test
public void getLastResults() throws Exception {
RespLastWyniki expected = Fake.generateFakeLastWynikiResponse();
when(lottoClient.soapService().getLastWyniki(anyString())).thenReturn(expected);
LastResults actual = lottoClientService.getLastResults();
Can someone tells me what's wrong ?
Error log:
java.lang.NullPointerException
at pl.lotto.service.LottoClientServiceImpl.getLastResults(LottoClientServiceImpl.java:26)
at pl.lotto.service.LottoClientServiceImplTest.getLastResults(LottoClientServiceImplTest.java:45)
and this is my service:
#Service
public class LottoClientServiceImpl implements LottoClientServiceInterface {
#Autowired
LottoClient lottoClient;
#Autowired
DozerBeanMapper mapper;
#Override
public LastResults getLastResults() {
try {
RespLastWyniki wyniki = lottoClient.soapService().getLastWyniki(new Date().toString());
LastResults result = mapper.map(wyniki, LastResults.class);
return result;
} catch (RemoteException e) {
throw new GettingDataError();
}
}
Ofcourse your dependency will be null,due to the #InjectMocks you are creating a new instance, outside the visibility of Spring and as such nothing will be auto wired.
Spring Boot has extensive testing support and also for replacing beans with mocks, see the testing section of the Spring Boot reference guide.
To fix it work with the framework instead of around it.
Replace #Mock with #MockBean
Replace #InjectMocks with #Autowired
Remove your setup method
Also apparently you only need a mock for the SOAP stub (so not sure what you need to mock for the LottoClient for).
Something like this should do the trick.
#SpringBootTest
public class LottoClientServiceImplTest {
#MockBean
SoapServiceBindingStub soapServiceBindingStub;
#Autowired
LottoClientServiceImpl lottoClientService;
#Test
public void getLastResults() throws Exception {
RespLastWyniki expected = Fake.generateFakeLastWynikiResponse();
when(soapServiceBindingStub.getLastWyniki(anyString())).thenReturn(expected);
LastResults actual = lottoClientService.getLastResults();