I want to test my implementation for AttributeConverter using #DataJpaTest.
test code
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class FooRepositoryTest {
#Autowired
private FooRepository repository;
#Test
void getPojoTest(){
FooEntity fooEnity= repository.findById("foo");
FooPojo fooPojo = fooEntity.getJsonPojo()
//some assertion
}
}
Entity
#Entity
#Data
#NoArgsConstructor
public class FooEntity{
....
#Column(columnDefinition= "JSON")
#Convert(converter = FooConverter.class)
private FooPojo data;
....
}
Attribute Converter
public class FooConverter implements AttributeConverter<FooPojo, String> {
#Autowired
private ObjectMapper mapper;
#SneakyThrows
#Override
public String convertToDatabaseColumn(FooPojo attribute) {
return mapper.writeValueAsString(attribute);
}
#SneakyThrows
#Override
public FooPojo convertToEntityAttribute(String dbData) {
return mapper.readValue(dbData, FooPojo.class);
}
}
with my code above, when I run getPojoTest(), the #autowired OjbectMapper in Converter is null. When I try the same test with #SpringBootTest instead, it works just fine. I wonder is there any walk-around to use #DataJpaTest and ObjectMapper together.
A better alternative compared to creating your own ObjectMapper is adding the #AutoConfigureJson annotation:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#AutoConfigureJson
public void FooRepositoryTest {
}
This is also what #JsonTest uses.
From Docs:
#DataJpaTest can be used if you want to test JPA applications. By
default it will configure an in-memory embedded database, scan for
#Entity classes and configure Spring Data JPA repositories. Regular
#Component beans will not be loaded into the ApplicationContext.
Related
When you test your code, you send a message to rabbit-mq. How do you get the message back when the test is over?
public interface RabbitProducer {
String OUTPUT = "rabbitmq_producer_channel";
#Output(OUTPUT)
MessageChannel output();
}
public class SysGroupServiceImpl {
#Autowired
private RabbitProducer rabbitProducer;
#Override
public Result remove(Collection<? extends Serializable> idList) {
rabbitProducer.output().send(MessageBuilder.withPayload(idList)
.setHeader("x-delay", 5000)
.setHeader("MessageType", "GroupBatchDelete").build());
return Result.booleanResult(true);
}
}
#SpringBootTest
#Transactional
#Rollback
public class SysGroupServiceTest {
#Autowired
private SysGroupService sysGroupService;
#Test
void removeTest(){
sysGroupService.remove(Stream.of("1").collect(Collectors.toList()));
}
}
I use Spring Cloud Stream to be compatible with RabbitMQ, and all the relevant code is there.Is there a way to mock this out?I tried the following scheme, but due to X-dealy, I got this error:No exchange type x-delayed-message
<dependency>
<groupId>com.github.fridujo</groupId>
<artifactId>rabbitmq-mock</artifactId>
</dependency>
#Component
public class RabbitMqMock {
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory(MockConnectionFactoryFactory.build());
}
}
I know little about mocks. Can mocks create an X-delay exchange?
I have a simple Controller class like below:-
#RestController
public class CrawlerAppController {
private static final Logger LOGGER = LoggerFactory.getLogger(CrawlerAppController.class);
#Autowired
private CrawlerServiceInterface crawlerService;
/* The response time of the crawling operation is directly proportional to the no of pages
* we want to crawl. Keeping a default value of 10 so we can view the results quicker.
* author: Arunava Paul
*/
#RequestMapping(value = "/crawl", method = { RequestMethod.GET })
public Object crawlUrl(#RequestParam(value = "URL") String URL,
#RequestParam(value = "max", defaultValue = "10") int maxPages) throws Exception {
if(!URL.startsWith("https://"))
URL="https://"+URL;
LOGGER.info("Request Received. Domain "+URL+" Pages to be Crawled "+maxPages);
return crawlerService.crawlService(URL, maxPages);
}
}
I have written a Junit class like below:-
#RunWith(PowerMockRunner.class)
public class CrawlerAppControllerTest {
Object obj=new Object();
#Spy
#InjectMocks
private CrawlerServiceInterface crawlerService = Mockito.any(CrawlerService.class);
#InjectMocks
CrawlerAppController appController = new CrawlerAppController();
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testController() throws Exception {
when(crawlerService.crawlService("https://vogella.com", 20)).thenReturn(obj);
assertEquals(appController.crawlUrl("vogella.com",20), obj);
}
}
It's always going into the Service class and the when statement is not running.
Can someone please advise what have I done wrong. Below error comes if I run Junit.
You should declare crawlerService like this:
#Mock
private CrawlerServiceInterface crawlerService;
The declaration of crawlerService in the test class should be:
#Mock
private CrawlerServiceInterface crawlerService;
We have spring boot with elasticsearch and mysql. We have a feature for reindexing all data from the mysql into elasticsearch, which is simple as:
#Service
#Transactional
public class SearchIndexer {
public void reindex(){
elasticsearchRepository.save(jpaRepository.findAll());
}
}
Now we have an entity called invoice, which has a lazy loaded collection with a "derived" calculation:
#Entity
#Table(name = "invoice")
#Document(indexName = "invoice")
public class Invoice implements Serializable {
//... other props
#OneToMany(fetch = FetchType.LAZY, mappedBy = "invoice")
#JsonIgnore
private Set<InvoiceItem> invoiceItems = new LinkedHashSet<>();
// getter and setters for invoiceItems
public boolean isAllSimple() {
if(getInvoiceType()==null){
return false;
}
if(getInvoiceItems()==null){
return false;
}
for(InvoiceItem item : getInvoiceItems()){
if(!item.isSimple()){
return false;
}
}
return true;
}
}
When the rest-controller is used, the resulting json contains correctly a property "allSimple". This is, because we run that with hibernate5module in one transaction.
However, when we call elasticsearchRepository.save(jpaRepository.findAll()) (also in a transaction), the objectmapper for elasticsearch cannot serialize the "allSimple" property, beacause of a LazyInitializationException. The elasticsearch-objectmapper is configured as follows:
#Bean
public ElasticsearchTemplate elasticsearchTemplate(Client client, Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder, Hibernate5Module hibernate5Module) {
return new ElasticsearchTemplate(client, new CustomEntityMapper(jackson2ObjectMapperBuilder.createXmlMapper(false).modulesToInstall(hibernate5Module).build()));
}
public class CustomEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
public CustomEntityMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure( DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
The hibernate5module is loaded and registered, but did not solve the problem.
Normally we would add a "JsonIgnore" to that property, but we need that value, so this is no option.
Any ideas?!
I had a project configurated with this.
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "com.sagasoftware.tracker.*")
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
Hibernate5Module hibernate5Module = new Hibernate5Module();
objectMapper.registerModule(hibernate5Module);
objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
messageConverter.setObjectMapper(objectMapper);
return messageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
}
If you are using spring boot, declaring the bean MappingJackson2HttpMessageConverter and registering the hibernate5module should fix your problem.
I could render a entity throught a rest controller.
I'm trying to unit test a Spring-boot controller and one of my #Autowired fields is coming back null.
I have two autowired fields in this controller:
public class UserProfileController{
#Autowired
private UserProfileService profileService;
#Autowired
private IDataValidator dataValidatorImpl;
My test class is as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#WebIntegrationTest
#SpringApplicationConfiguration(classes = UserProfileServiceApplication.class)
public class ControllerTest {
private MockMvc mockMvc;
#Mock
UserProfileService profileServiceMock;
#Autowired
ApplicationContext actx;
#InjectMocks
private UserProfileController profileController;
#Before
public void setup() {
// Process mock annotations
String[] asdf = actx.getBeanDefinitionNames();
for (int i = 0; i < asdf.length; i++){
System.out.println(asdf[i]);
}
MockitoAnnotations.initMocks(this);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(profileController).build();
}
/**
* All this does is verify that we return the correct datatype and HTTP status
* #throws Exception
*/
#Test
public void testGetProfileSuccess() throws Exception {
Mockito.when(profileServiceMock.getProfile(Mockito.any(HashMap.class))).thenReturn(new HashMap<String, Object>());
mockMvc.perform(get("http://localhost:8095/UserName?tenantId=tenant1"))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8));
//verify profileService was only used once
Mockito.verify(profileServiceMock, Mockito.times(1)).getProfile(Mockito.any(HashMap.class));
//verify we're done interacting with profile service
Mockito.verifyNoMoreInteractions(profileServiceMock);
}
If I leave IDataValidator untouched in the test class, it comes up null and I get a NPE. If I #Spy the DataValidatorImpl, it cannot find properties from the Spring environment that it needs to work.
How can I just let the IDataValidator autowire itself and maintain its spring environment context as if I were just running the application normally?
When I print all beans in my #Before setup() method, I can see DataValidationImpl in the list.
When you mock your controller with
MockMvcBuilders.standaloneSetup(profileController).build();
the controller is replaced in the context. Since you did not inject any IDataValidator in it, it is null.
The simplest solution is to autowired the real IDataValidator into your test class and inject it into the controller.
In your controller:
public class UserProfileController{
private UserProfileService profileService;
private IDataValidator dataValidatorImpl;
#Autowired
public UserProfileController(UserProfileService profileService, IDataValidator dataValidatorImpl) {
this.profileService = profileService;
this.dataValidatorImpl = dataValidatorImpl;
}
And in your test :
#RunWith(SpringJUnit4ClassRunner.class)
#WebIntegrationTest
#SpringApplicationConfiguration(classes = UserProfileServiceApplication.class)
public class ControllerTest {
private MockMvc mockMvc;
private UserProfileService profileService;
#Autowired
private IDataValidator dataValidator;
#Before
public void setup() {
UserProfileService profileService = Mockito.mock(UserProfileService.class);
UserProfileController controller = new UserProfileController(profileService, dataValidator);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
}
If I understand correctly, you want to inject UserProfileController with real Validator and mock Service.
In this case I suggest to use #ContextConfiguration annotaion which allows to configure context in the test. You'll need to create a Configuration class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebIntegrationTest
#SpringApplicationConfiguration(classes = UserProfileServiceApplication.class)
public class ControllerTest {
private MockMvc mockMvc;
#Mock
UserProfileService profileServiceMock;
#Autowired
ApplicationContext actx;
//comment this line out
//#InjectMocks
#Autowired
private UserProfileController profileController;
#Before
public void setup() {
// Process mock annotations
String[] asdf = actx.getBeanDefinitionNames();
for (int i = 0; i < asdf.length; i++){
System.out.println(asdf[i]);
}
//comment this line out
//MockitoAnnotations.initMocks(this);
#Configuration
public static class Config {
//wire validator - if it is not wired by other configurations
#Bean
Validator validator() {
return new Validaor();
}
//wire mock service
#Bean
public UserProfileService profileService() {
return mock(UserProfileService.class);
}
}
Okay, I swear I did this the first time but when trying to recreate the error thrown for jny it actually worked.
My solution is to inject via #Spy annotation and get the bean from the ApplicationContext in my #Before setup method.
public class ControllerTest {
private MockMvc mockMvc;
#Mock
UserProfileService profileServiceMock;
#Spy
IDataValidator dataValidator;
#Autowired
ApplicationContext actx;
#InjectMocks
private UserProfileController profileController;
#Before
public void setup() {
dataValidator = (IDataValidator) actx.getBean("dataValidatorImpl");
MockitoAnnotations.initMocks(this);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(profileController).build();
}
In a previous similar question, I asked about, how to serialise two different sets of fields using JacksonJson and Spring.
My use case is the typical Controller mapping with #ResponseBody annotation returning directly a particular object or collections of objects, that are then rendered with JacksonJson whenever the client adds application/json in the Accept header.
I had two answers, the first one suggests to return different interfaces with a different getter list, the second suggests to use Json Views.
I don't have problems to understand the first way, however, for the second, after reading the documentation on JacksonJsonViews, I don't know how to implement it with Spring.
To stay with the example, I would declare three stub classes, inside the class Views:
// View definitions:
public class Views {
public static class Public { }
public static class ExtendedPublic extends PublicView { }
public static class Internal extends ExtendedPublicView { }
}
Then I've to declare the classes mentioned:
public class PublicView { }
public class ExtendedPublicView { }
Why on earth they declare empty static classes and external empty classes, I don't know. I understand that they need a "label", but then the static members of Views would be enough. And it's not that ExtendedPublic extends Public, as it would be logical, but they are in fact totally unrelated.
And finally the bean will specify with annotation the view or list of views:
//changed other classes to String for simplicity and fixed typo
//in classname, the values are hardcoded, just for testing
public class Bean {
// Name is public
#JsonView(Views.Public.class)
String name = "just testing";
// Address semi-public
#JsonView(Views.ExtendedPublic.class)
String address = "address";
// SSN only for internal usage
#JsonView(Views.Internal.class)
String ssn = "32342342";
}
Finally in the Spring Controller, I've to think how to change the original mapping of my test bean:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() {
return new Bean();
}
It says to call:
//or, starting with 1.5, more convenient (ObjectWriter is reusable too)
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);
So I have an ObjectMapper instance coming out of nowhere and an out which is not the servlet typical PrintWriter out = response.getWriter();, but is an instance of JsonGenerator and that can't be obtained with the new operator. So I don't know how to modify the method, here is an incomplete try:
#RequestMapping(value = "/bean")
#ResponseBody
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator out; //how to create?
objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean());
return ??; //what should I return?
}
So I would like to know if anybody had success using JsonView with Spring and how he/she did. The whole concept seems interesting, but the documentation seems lacking, also the example code is missing.
If it's not possible I will just use interfaces extending each others. Sorry for the long question.
Based on the answers by #igbopie and #chrislovecnm, I've put together an annotation driven solution:
#Controller
public class BookService
{
#RequestMapping("/books")
#ResponseView(SummaryView.class)
public #ResponseBody List<Book> getBookSummaries() {}
#RequestMapping("/books/{bookId}")
public #ResponseBody Book getBook(#PathVariable("bookId") Long BookId) {}
}
Where SummaryView is annotated on the Book model like so:
#Data
class Book extends BaseEntity
{
#JsonView(SummaryView.class)
private String title;
#JsonView(SummaryView.class)
private String author;
private String review;
public static interface SummaryView extends BaseView {}
}
#Data
public class BaseEntity
{
#JsonView(BaseView.class)
private Long id;
}
public interface BaseView {}
A custom HandlerMethodReturnValueHandler is then wired into Spring MVC's context to detect the #ResponseView annotation, and apply the Jackson view accordingly.
I've supplied full code over on my blog.
You need to manually wire in the MappingJacksonHttpMessageConverter. In spring 3.1 you are able to use the mvc xml tags like the following:
<mvc:annotation-driven >
<mvc:message-converter>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
It is pretty ugly to not use spring 3.1, it will save you about 20 lines of xml. The mvc:annotation tag does ALOT.
You will need to wire in the object mapper with the correct view writer. I have noticed recently the using a #Configuration class can make complicated wiring like this a lot easier. Use a #Configuration class and create a #Bean with your MappingJacksonHttpMessageConverter, and wire the reference to that bean instead of the MappingJacksonHttpMessageConverter above.
I've manage to solve the problem this way:
Create custom abstract class to contain the json response object:
public abstract AbstractJson<E>{
#JsonView(Views.Public.class)
private E responseObject;
public E getResponseObject() {
return responseObject;
}
public void setResponseObject(E responseObject) {
this.responseObject = responseObject;
}
}
Create a class for each visibility (just to mark the response):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
Change your method declaration:
#RequestMapping(value = "/bean")
#ResponseBody
public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
return new PublicJson(new Bean());
}
Create customs MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public PublicApiResponseMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(PublicJson.class)){
return true;
}
return false;
}
}
public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public ExtendedPublicJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(ExtendedPublicJson.class)){
return true;
}
return false;
}
}
public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{
public InternalJsonMessageConverter(){
super();
org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class));
this.setObjectMapper(objMapper);
}
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
if(clazz.equals(Internal.class)){
return true;
}
return false;
}
}
Add the following to your xml:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="PublicJsonMessageConverter"></bean>
<bean class="ExtendedPublicJsonMessageConverter"></bean>
<bean class="InternalJsonMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
That's it! I had to update to spring 3.1 but that's all. I use the responseObject to send more info about the json call but you can override more methods of the MessageConverter to be completely transparent. I hope someday spring include an annotation for this.
Hope this helps!