Test multipart PUT request with json data using mockMvc - spring

I am trying to unit test a put request which takes a file and some json data as request body. following is the method i am trying to test:
#RequestMapping(
value = "/{id}",
method = RequestMethod.PUT,
produces = { "application/json" }
)
public ResponseEntity<UpdateT1Output> update(#PathVariable String id, #ModelAttribute #Valid UpdateT1Input t1) {
// implementation here
}
UpdateT1Input.java
public class UpdateT1Input {
private char[] ca;
private byte[] file;
public void setFile(MultipartFile mpfile) {
try {
file = mpfile.getBytes();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private List<Double> flpa;
private List<Double> fpa;
#NotNull(message = "id Should not be null")
private Long id;
private String str;
private Long versiono;
}
test setup
#Test
public void UpdateT1_T1Exists_ReturnStatusOk() throws Exception {
// create entity obj with default values
T1Entity entity = createUpdateEntity();
entity.setVersiono(0L);
UpdateT1Input t1Input = new UpdateT1Input();
t1Input.setId(entity.getId());
t1Input.setFlpa(entity.getFlpa());
t1Input.setStr(entity.getStr());
ObjectWriter ow = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.writer()
.withDefaultPrettyPrinter();
String json = ow.writeValueAsString(t1Input);
MockMultipartHttpServletRequestBuilder builder =
MockMvcRequestBuilders.multipart("/t1/" + entity.getId());
builder.with(request -> {
request.setMethod("PUT");
return request;
});
mvc.perform(builder
.file("file", "ABC".getBytes("UTF-8"))
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk());
}
but in controller only id and file fields are set in input dto all other fields are null. i am using #ModelAttribute to avoid dividing request into file and data parts. so is there a way that to get all the fields in single object?

Related

Spring-boot MultipartFile issue with ByteArrayResource

I'm trying to implement a rest api consuming excel file. I'm using spring-boot and code is available here.
Code works fine when using FileSystemResource for payload. But i'm not able to make the code work with ByteArrayResource in replacement of FileSystemResource:
RestApi.java:
#RestController
public class RestApi {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#PostMapping("/api/upload")
public ResponseEntity<?> uploadFile(#RequestParam("file") MultipartFile uploadfile) {
LOGGER.debug("Single file upload!");
try {
LOGGER.info("\n\n ****** File name: {}, type {}! ************", uploadfile.getOriginalFilename(), uploadfile.getContentType());
this.processExcelFile(uploadfile.getInputStream());
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK);
}
private List<String> processExcelFile(InputStream stream) throws Exception {
List<String> result = new ArrayList<String>();
//Create Workbook instance holding reference to .xlsx file
try(XSSFWorkbook workbook = new XSSFWorkbook(stream);) {
//Get first/desired sheet from the workbook
XSSFSheet sheet = workbook.getSheetAt(0);
//Iterate through each rows one by one
Iterator<Row> rowIterator = sheet.iterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
String cellValue = row.getCell(0).getRichStringCellValue().toString();
result.add(cellValue);
LOGGER.info("\n\n ****** Cell value: {} ************", cellValue);
}
return result;
}
}
}
RestApiTest:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RestApiTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private ResourceLoader loader;
#Test
public void testUploadFile() throws Exception {
Resource resource = this.loader.getResource("classpath:test.xlsx");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
// parts.add("file", new FileSystemResource(resource.getFile()));
parts.add("file", new ByteArrayResource(IOUtils.toByteArray(resource.getInputStream())));
String response = this.restTemplate.postForObject("/api/upload", parts, String.class);
Assertions.assertThat(response).containsIgnoringCase("success");
}
}
I'm getting following error when running test:
java.lang.AssertionError:
Expecting:
<"{"timestamp":1487852597527,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/api/upload"}">
to contain:
<"success">
(ignoring case)
Any idea?
when using loader.getResource(...) you must use resource itself as answered above. So you don't need ByteArrayResource. I got this problem, but I'm not using resource from classpath. So if someone really need to use ByteArrayResource, here is my workaround
public class FileNameAwareByteArrayResource extends ByteArrayResource {
private String fileName;
public FileNameAwareByteArrayResource(String fileName, byte[] byteArray, String description) {
super(byteArray, description);
this.fileName = fileName;
}
#Override
public String getFilename() {
return fileName;
}
}
and then use it
parts.add("file", new FileNameAwareByteArrayResource("filename", byteArray));

#JsonInclude not working on Deserialization with Spring, using mutable objects

I have found a lot about this, but no one having this same issue, the only that i can think is the last answer in this question where the mutability of the object makes the annotation work different.
So, I have an object like this
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(value = Include.NON_EMPTY)
public class Invoice implements Serializable {
#JsonInclude(value = Include.NON_EMPTY)
private String originCode;
#JsonInclude(value = Include.NON_EMPTY)
public String getOriginCode() {
return originCode;
}
#JsonInclude(value = Include.NON_EMPTY)
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
}
When deserializing this object from a JSON, using spring framework the value of originCode keeps coming empty, if i use
{
"originCode" : ""
}
In the other way around if I use this object where originCode is already empty and I serialize it, the originCode is ignores in the serialized json.
Why this is working just when serializing?, how the fact that this object is mutable can affect in the use of this annotation when deserializing?
---EDIT---
The solution proposed here below did not fix the problem, I thought the problem was actually in spring. So I tried like this
#RequestMapping(method = RequestMethod.POST, value = "/test")
#ResponseBody
public ResponseEntity<InvMessage> testInvoice(
#PathVariable(value = "someId") #Example({ "1233232-7" }) InvoicerId invoicerId,
#RequestBody Invoice2 invoiceRequest,
InvMessage messageContainer) {
ObjectMapper mapper = new ObjectMapper();
try {
String jsonString1 = mapper.writeValueAsString(invoiceRequest);
Invoice2 invoiceTest1 = mapper.readValue(jsonString1, Invoice2.class);
String jsonInString2 = "{\"originCode\" : \"\",\"originText\" : \"Original\"}";
Invoice2 testInvoice = mapper.readValue(jsonInString2, Invoice2.class);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ok(messageContainer);
}
So, when sending a request with body
{
"originCode" : "",
"originText" : "Original"
}
The results are
jsonString1: "{"originText" : "Original"}"
invoiceTest1 (originCode null)
invoiceTest2 (originCode: "")
Checking those results, i can see that always when deserializing that empty string I'm getting also an empty string inside the object, even I have defined the class like.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Invoice2 implements Serializable {
private static final long serialVersionUID = 1L;
#JsonInclude(value = Include.NON_EMPTY)
private String originCode;
private String originText;
public String getOriginCode() {
return originCode;
}
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
public String getOriginText() {
return originText;
}
public void setOriginText(String originText) {
this.originText = originText;
}
}
Jackson-databind version 2.5
Since you may be using Class level annotation and property level annotation, latter overriding the former explained here
Try,
#JsonIgnoreProperties(ignoreUnknown = true)
public class Invoice implements Serializable {
#JsonInclude(value = Include.NON_EMPTY)
private String originCode;
public String getOriginCode() {
return originCode;
}
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
}

Java: GroupSequenceProvider for Validation, object is null in getValidationGroups method

This is what I am trying to achieve:
I have an update request object and user is allowed to do Partial Updates. But I want to validate the field only if it is in the request body. Otherwise, it is OK to be null. To achieve this, I am using GroupSequenceProvider to let the Validator know what groups to validate. What am I doing wrong here? If there is a blunder, how do I fix it?
Documentation: https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html#example-implementing-using-default-group-sequence-provider
#GroupSequenceProvider(UpdateUserRegistrationGroupSequenceProvider.class)
public class UpdateUserRegistrationRequestV1 {
#NotBlank(groups = {EmailExistsInRequest.class})
#Email(groups = {EmailExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {EmailExistsInRequest.class})
private String email;
#NotNull(groups = {PasswordExistsInRequest.class})
#Size(min = 8, max = 255, groups = {PasswordExistsInRequest.class})
private String password;
#NotNull(groups = {FirstNameExistsInRequest.class})
#Size(max = 255, groups = {FirstNameExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {FirstNameExistsInRequest.class})
private String firstName;
// THERE ARE GETTERS AND SETTERS BELOW
}
Group Sequence Provider Code:
public class UpdateUserRegistrationGroupSequenceProvider implements DefaultGroupSequenceProvider<UpdateUserRegistrationRequestV1> {
public interface EmailExistsInRequest {}
public interface PasswordExistsInRequest {}
public interface FirstNameExistsInRequest {}
#Override
public List<Class<?>> getValidationGroups(UpdateUserRegistrationRequestV1 updateUserRegistrationRequestV1) {
List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
defaultGroupSequence.add(Default.class);
defaultGroupSequence.add(UpdateUserRegistrationRequestV1.class);
if(StringUtils.hasText(updateUserRegistrationRequestV1.getEmail())) {
defaultGroupSequence.add(EmailExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getPassword())) {
defaultGroupSequence.add(PasswordExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getFirstName())) {
defaultGroupSequence.add(FirstNameExistsInRequest.class);
}
return defaultGroupSequence;
}
}
I am using Spring MVC, so this is how my controller method looks,
#RequestMapping(value = "/{userId}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void updateUser(#PathVariable("userId") Long userId,
#RequestBody #Valid UpdateUserRegistrationRequestV1 request) {
logger.info("Received update request = " + request + " for userId = " + userId);
registrationService.updateUser(userId, conversionService.convert(request, User.class));
}
Now the problem is, the parameter "updateUserRegistrationRequestV1" in the UpdateUserRegistrationGroupSequenceProvider.getValidationGroups method is null. This is the request object that I am sending in the request body and I am sending email field with it.
What am I doing wrong?
I too went through the same issue ,and hopefully solved it
You just have to check the object is null and put all your conditions inside it.
public List<Class<?>> getValidationGroups(Employee object) {
List<Class<?>> sequence = new ArrayList<>();
//first check if the object is null
if(object != null ){
if (!object.isDraft()) {
sequence.add(Second.class);
}
}
// Apply all validation rules from default group
sequence.add(Employee.class);
return sequence;
}

Testing with Mockito

I'm trying to test some services with Mockito but I have problems when the main class that I test and where I inject Mocks calls to super.
I run the project with spring and these are the steps I follow to get the error.
Here is where I create the test
public class UrlShortenerTests {
private MockMvc mockMvc;
#Mock
private ShortURLRepository shortURLRepository;
#Mock
private ClickRepository clickRespository;
#InjectMocks
private UrlShortenerControllerWithLogs urlShortenerWL;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(urlShortenerWL).build();
}
#Test
public void thatShortenerCreatesARedirectIfTheURLisOK() throws Exception {
mockMvc.perform(post("/link")
.param("url", "http://www.google.com"))
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.target", is("http://example.com/")));
}
}
Here is the class UrlShortenerControllerWithLogs with the method shortener, which is the one I want to test with the previous POST call
#RestController
public class UrlShortenerControllerWithLogs extends UrlShortenerController {
#Autowired
private ClickRepository clickRepository;
#Autowired
private ShortURLRepository SURLR;
public ResponseEntity<ShortURL> shortener(#RequestParam("url") String url,
#RequestParam(value = "sponsor", required = false) String sponsor,
#RequestParam(value = "brand", required = false) String brand,
HttpServletRequest request) {
ResponseEntity<ShortURL> su = super.shortener(url, sponsor, brand,
request);
return su;
}
And this is the super class
#RestController
public class UrlShortenerController {
#Autowired
protected ShortURLRepository shortURLRepository;
#Autowired
protected ClickRepository clickRepository;
#RequestMapping(value = "/link", method = RequestMethod.POST)
public ResponseEntity<ShortURL> shortener(#RequestParam("url") String url,
#RequestParam(value = "sponsor", required = false) String sponsor,
#RequestParam(value = "brand", required = false) String brand,
HttpServletRequest request) {
ShortURL su = createAndSaveIfValid(url, sponsor, brand, UUID
.randomUUID().toString(), extractIP(request));
if (su != null) {
HttpHeaders h = new HttpHeaders();
h.setLocation(su.getUri());
return new ResponseEntity<>(su, h, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
protected ShortURL createAndSaveIfValid(String url, String sponsor,
String brand, String owner, String ip) {
UrlValidator urlValidator = new UrlValidator(new String[] { "http",
"https" });
if (urlValidator.isValid(url)) {
String id = Hashing.murmur3_32()
.hashString(url, StandardCharsets.UTF_8).toString();
ShortURL su = new ShortURL(id, url,
linkTo(
methodOn(UrlShortenerController.class).redirectTo(
id, null)).toUri(), sponsor, new Date(
System.currentTimeMillis()), owner,
HttpStatus.TEMPORARY_REDIRECT.value(), true, ip, null);
return shortURLRepository.save(su);
} else {
return null;
}
}
So, when I call to shortURLRepository.save(su) in the second method (createAndSaveIfValid), it never enters in the method save, so it returns me null instead of the object I want.
The code of the implementation of ShortURLRepository and the method save is:
#Repository
public class ShortURLRepositoryImpl implements ShortURLRepository {
private static final Logger log = LoggerFactory
.getLogger(ShortURLRepositoryImpl.class);
#Override
public ShortURL save(ShortURL su) {
try {
jdbc.update("INSERT INTO shorturl VALUES (?,?,?,?,?,?,?,?,?)",
su.getHash(), su.getTarget(), su.getSponsor(),
su.getCreated(), su.getOwner(), su.getMode(), su.getSafe(),
su.getIP(), su.getCountry());
} catch (DuplicateKeyException e) {
log.debug("When insert for key " + su.getHash(), e);
return su;
} catch (Exception e) {
log.debug("When insert", e);
return null;
}
return su;
}
I think that the problem is that the object ShortURLRepository created in the test class is not initialized on the super class (UrlShortenerController) or something similar.
Is it possible?
Can anybody help me?
The full code is in: https://github.com/alberto-648702/UrlShortener2014
The class UrlShortenerTests is in:
bangladeshGreen/src/test/java/urlshortener2014/bangladeshgreen
The class UrlShortenerControllerWithLogs is in:
bangladeshGreen/src/main/java/urlshortener2014/bangladeshgreen/web
The class UrlShortenerController is in:
common/src/main/java/urlshortener2014/common/web
The class ShortURLRepositoryImpl is in:
common/src/main/java/urlshortener2014/common/repository
This is not an error. This is the expected behaviour. #Mock creates a mock. #InjectMocks creates an instance of the class and injects the mocks that are created with the #Mock. A mock is not a real object with known values and methods. It is an object that has the same interface as the declared type but you control its behaviour. By default the mocked object methods do nothing (e.g. return null). Therefore if ShortURLRepository is mocked and injected in UrlShortenerControllerWithLogs calling save in the injected ShortURLRepository does not call the real code as you expected, it does nothing. If you want to mock the behaviour of save, add the following code in your setup:
when(shortURLRepository.save(org.mockito.Matchers.any(ShortURL.class))).
then(new Answer<ShortURL>() {
#Override
public ShortURL answer(InvocationOnMock invocation) throws Throwable {
ShortURL su = (ShortURL) invocation.getArguments()[0];
// Do something with su if needed
return su;
}
});

Spring Rest Issue

I am getting an error while i am trying to test my "testCreateUser" method using Spring RestApi, the uploadNewUser.xml contains the login information about the user and the role.
#Test
public void testCreateUser() throws Exception {
Reader reader = getFileReader("src/test/resources/uploadNewUser.xml");
String input_xml = IOUtils.toString(reader);
byte[] content = input_xml.getBytes();
request.addHeader("Accept", "application/xml");
request.addHeader("Content-Type", "application/xml");
request.setContent(content);
request.setContentType("text/xml");
request.setMethod(RequestMethod.POST.name());
request.setRequestURI("/restapi/users/");
final ModelAndView mav = handle(request, response);
Map<String, Object> map = mav.getModel();
for (Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
UserCollection collection = (UserCollection) entry.getValue();
org.springframework.validation.BindingResult.error = com.xxx.dashboard.restapi.GlobalResponse#42a4fd6d
error stack:
java.lang.ClassCastException: com.xxx.dashboard.restapi.GlobalResponse cannot be cast to com.xxx.dashboard.restapi.UserCollection
and i am getting an issue with cannot cast GlobalRespose to UserCollection. can anyone tell me where exactly i am doing is wrong? any help or pointers are most welcome thanks in advance
#Controller("userrestapi")
#RequestMapping(value = { "/restapi/users/", "/restapi/users" })
public class UserRestApi extends AbstractBaseApi {
...
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.CREATED)
public ModelAndView createNewUser(#RequestBody UserCollection userCollection,
#RequestHeader(value = "accept", required = false) String accept,
#RequestHeader(value = "version", required = false) String version) {
try {
OOUser ooUser = userCollection.getUsers().get(0);
Mapper mapper = (Mapper) userVersions.get(Constants.USER_DETAIL_VERSION_MAPPER_KEY);
int userId = usersRestApiService.validateAndCreateNewUser(ooUser, mapper);
List<FilterField> filterFieldList = new ArrayList<FilterField>();
filterFieldList.add(new FilterField("userId", String.valueOf(userId)));
return getUserDetailsForFilter(filterFieldList, accept, version, mapper);
} catch (Exception ex) {
logger.warn("Api exception", ex);
return getModelAndView(accept, "error", getGlobalResponse(ex));
}
the abstractbaseapi contains following
public class AbstractBaseApi {
public static final String XML_VIEW = "apiXmlView";
public static final String JSON_VIEW = "apiJsonView";
public static final String JSON_ACCEPT_HEADER = "application/json";
public static final String JSON_CONTENT_HEADER = "Content-type: application/json";
public static final String XML_CONTENT_HEADER = "Content-type: text/html;charset=utf-8";
public static final int MAX_COUNT = 100;
public static final String XML_REQUEST_ERROR_FORMAT = "<?xml version='1.0' encoding='UTF-8'?><GlobalResponse xmlns='http://www.operative.com/api' xmlns:v2='http://www.operative.com/api/v2' xmlns:v1='http://www.operative.com/api/v1'> <error errorCode='%1$s' text='%2$s'/> </GlobalResponse>";
public static final String JSON_REQUEST_ERROR_FORMAT = "{error:{errorCode:'%1$s',text:'%2$s'}}";
protected final Logger logger = Logger.getLogger(this.getClass());
protected ModelAndView getModelAndView(String accept, String key, Object value) {
String view = XML_VIEW;
if (accept != null && accept.toLowerCase().contains(JSON_ACCEPT_HEADER)) {
view = JSON_VIEW;
}
if (logger.isDebugEnabled()) {
logger.debug("Accept Header:" + accept + " , generating:" + view);
}
return new ModelAndView(view, BindingResult.MODEL_KEY_PREFIX + key, value);
}
Your model contains more than you think.
You are going through your model and looking for your user collection. However, the first encountered object in your map seems to be the GlobalResponse map.
You should probably just get it by name from the model, i.e.
UserCollection collection = (UserCollection) mav.getModel().get("userCollection");
rather than iterating..

Resources