How to unit test a multipart POST request with Spring MVC Test? - spring-boot

I am trying to create unit test for REST APi but having big trouble with the uploading excel method.
Here is the method on the controller side
#RestController()
#RequestMapping(path = "/upload")
#CrossOrigin(origins = "http://localhost:4200")
public class FileController {
#Autowired
FileService fileService;
#PostMapping(value = "/{managerId}/project/{projectId}")
public List<Task> importExcelFile(#RequestParam("file") MultipartFile files, #PathVariable int managerId,
#PathVariable int projectId) throws IOException, ParseException {
return fileService.getTasksFromExcel(files, managerId, projectId);
}
Whatever I try I get a lot of errors and evidently I don't really understand what I am supposed to do.
The main error I get is
current request is not a multipart request

You can do the following.
I just simplified your example a tiny bit.
So, here's the controller that returns the file size of the file it receives.
#RestController
#RequestMapping(path = "/upload")
public class FileController {
#PostMapping(value = "/file")
public ResponseEntity<Object> importExcelFile(#RequestParam("file") MultipartFile files) {
return ResponseEntity.ok(files.getSize());
}
}
and this one is the test of it. There is a class called MockMvc that Spring provides to easily unit test your controllers and controller advices. There is a method called multipart that you can use to simulate file upload cases.
class FileControllerTest {
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new FileController())
.build();
#Test
#SneakyThrows
void importExcelFile() {
final byte[] bytes = Files.readAllBytes(Paths.get("TEST_FILE_URL_HERE"));
mockMvc.perform(multipart("/upload/file")
.file("file", bytes))
.andExpect(status().isOk())
.andExpect(content().string("2037")); // size of the test input file
}
}

Generally Multipart uploads can be tested via MockMultipartFile:
https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/file-upload-test.html

Related

Spring Mockito test of RestTemplate.postForEntity throws IllegalArgumentException: URI is not absolute

My Controller calls the service to post information about a car like below and it works fine. However, my unit test fails with the IllegalArgumentException: URI is not absolute exception and none of the posts on SO were able to help with it.
Here is my controller
#RestController
#RequestMapping("/cars")
public class CarController {
#Autowired
CarService carService;
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CarResponse> getCar(#RequestBody CarRequest carRequest, #RequestHeader HttpHeaders httpHeaders) {
ResponseEntity<CarResponse> carResponse = carService.getCard(carRequest, httpHeaders);
return carResponse;
}
}
Here is my service class:
#Service
public class MyServiceImpl implements MyService {
#Value("${myUri}")
private String uri;
public void setUri(String uri) { this.uri = uri; }
#Override
public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) {
List<String> authHeader = httpHeaders.get("authorization");
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", authHeader.get(0));
HttpEntity<CarRequest> request = new HttpEntity<CarRequest>(carRequest, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class);
return cardResponse;
}
}
However, I am having trouble getting my unit test to work. The below tests throws IllegalArgumentException: URI is not absolute exception:
public class CarServiceTest {
#InjectMocks
CarServiceImpl carServiceSut;
#Mock
RestTemplate restTemplateMock;
CardResponse cardResponseFake = new CardResponse();
#BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
cardResponseFake.setCarVin(12345);
}
#Test
final void test_GetCars() {
// Arrange
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", anyString());
ResponseEntity<CarResponse> carResponseEntity = new ResponseEntity(carResponseFake, HttpStatus.OK);
String uri = "http://FAKE/URI/myapi/cars";
carServiceSut.setUri(uri);
when(restTemplateMock.postForEntity(
eq(uri),
Mockito.<HttpEntity<CarRequest>> any(),
Mockito.<Class<CarResponse>> any()))
.thenReturn(carResponseEntity);
// Act
**// NOTE: Calling this requires real uri, real authentication,
// real database which is contradicting with mocking and makes
// this an integration test rather than unit test.**
ResponseEntity<CarResponse> carResponseMock = carServiceSut.getCar(carRequestFake, headers);
// Assert
assertEquals(carResponseEntity.getBody().getCarVin(), 12345);
}
}
UPDATE 1
I figured out why the "Uri is not absolute" exection is thrown. It is because in my carService above, I use #Value to inject uri from application.properties file, but in unit tests, that is not injected.
So, I added public property to be able to set it and updated the code above, but then I found that the uri has to be a real uri to a real backend, requiring a real database.
In other words, if the uri I pass is a fake uri, the call to carServiceSut.getCar above, will fail which means this turns the test into an integration test.
This contradicts with using mocking in unit tests.
I dont want to call real backend, the restTemplateMock should be mocked and injected into carServiceSut since they are annotated as #Mock and #InjectMock respectively. Therefore, it whould stay a unit test and be isolated without need to call real backend. I have a feeling that Mockito and RestTemplate dont work well together.
You need to construct your system under test properly.
Currently, MyServiceImpl.uri is null.
More importantly, your mock of RestTemplate is not injected anywhere, and you construct a new RestTemplate in method under test.
As Mockito has no support for partial injection, you need to construct the instance manually in test.
I would:
Use constructor injection to inject both restTemplate and uri:
#Service
public class MyServiceImpl implements MyService {
private RestTemplate restTemplate;
private String uri;
public MyServiceImpl(RestTemplate restTemplate, #Value("${myUri}") uri) {
this.restTemplate = restTemplate;
this.uri = uri;
}
Construct the instance manually:
drop #Mock and #InjectMocks
drop Mockito.initMocks call
use Mockito.mock and constructor in test
public class CarServiceTest {
public static String TEST_URI = "YOUR_URI";
RestTemplate restTemplateMock = Mockito.mock(RestTemplate.class);
CarServiceImpl carServiceSut = new CarServiceImpl(restTemplateMock, TEST_URI):
}
Remove creation of restTemplate in method under test.
If needed, add a config class providing RestTemplate bean (for the application, the test does not need that):
#Configuration
public class AppConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Note that RestTemplate is thread-safe, one instance per app is enough: Is RestTemplate thread safe?
try to change the URI as
String uri = "http://some/fake/url";

Spring Security and MockMvc - Need to mock authentication or principal

I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.
I have a method in my controller that goes something like this:
#GetMapping
public ResponseEntity<User> getUser(#AuthenticationPrincipal User activeUser){
String userEmail = activeUser.getEmail();
return userService.getUser(userEmail);
}
I get a 500 error with this.
Another variation for the controller I've tried is, and this is working on Postman/Curl :
#GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
return userService.getUser(userEmail);
}
My service looks like :
public ResponseEntity<User> getUser(String email) {
return userRepository.findByEmail(email)
.map(record -> ResponseEntity.ok().body(record))
.orElse(ResponseEntity.notFound().build());
}
In my unit test case for this controller method, I have:
#Test
#WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}
I am getting the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)
How should I go about passing or mocking a user with the current authentication? Thanks.
#WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.
At least three solutions here:
Inject an OAuth2Authentication mock or instance in the security context
change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
use some existing tooling to apply solution 1. for you, like in this libs I wrote
Sample usage with solution 1
#Test
public void test() throws Exception {
final var storedRequest = mock(OAuth2Request);
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var userAuthentication = mock(Authentication.class);
when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
when(userAuthentication.getPrincipal()).thenReturn(principal);
final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);
SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
Sample usage with solution 3
#Test
#WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:
Try Mocking OAuth2Authentication in some setUp method.
OR
If you are using Spring 4.0+, the best solution is to annotate the test method with #WithMockUser
#Test
#WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
//Your test scenario
}

RestTemplate Spring Boot receive the file

In my Spring Boot application I have implemented the RestController method that returns the file in response.getOutputStream():
#RequestMapping(value = "/{fileId}", method = RequestMethod.GET)
#ResponseStatus(value = HttpStatus.OK)
public void getFile(#PathVariable #NotBlank String fileId, HttpServletResponse response) throws IOException, TelegramApiException {
File file = fileService.getFile(fileId);
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
response.setContentType(FileUtils.detectMimeType(inputStream));
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("inline; filename=%s", file.getName()));
response.setContentLength((int) file.length());
FileCopyUtils.copy(inputStream, response.getOutputStream());
}
I would like to implement the integration test and use RestTemplate in order to invoke this endpoint and receive the file.
Something like:
restTemplate.getForObject(String.format("%s/v1.0/files/%s", getBaseApiUrl(), fileId), SOMECLASS.class);
Could you please show an example how it can be properly archived with Spring RestTemplate ?
I am assuming you want to start the server and call that API to see if it really works. Using spring-test, you can do this:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/" + 1234, // File ID
String.class)).contains("File Content Here");
}
}
But you can actually test your logic only without start the server itself. Please refer to https://spring.io/guides/gs/testing-web/ for more info.
I have found the solution:
ResponseEntity<Resource> responseEntity = restTemplate.exchange(String.format("%s/v1.0/files/%s", getBaseApiUrl(), fileId), HttpMethod.GET, null, Resource.class);
return responseEntity.getBody().getInputStream();

Spring boot MockMvc post not enter to real conntroller method

I have controllers method
#PostMapping(value = "/getTransaction/{transactionUuid}")
public ResponseEntity<TransactionDetail> getTransaction(#PathVariable() String transactionUuid) {
return ResponseEntity.ok(transactionsService.getOpcTransaction(transactionUuid));
}
I write test for controller:
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
mockMvc.perform(post("/nfp-server/getTransaction/{transactionUuid}", "123"))
.andDo(print());
}
I star test in debug mode - but I not enter to controller getTransaction method. And in log I see:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /server/getTransaction/123
Parameters = {}
Headers = {}
In your controller your mapping is
/getTransaction/{transactionUuid}
In your test class you are passing
/nfp-server/getTransaction/{transactionUuid}
There is a mismatch.

Spring custom validator class with MultipartFile class support does not get called/fired

Sorry i couldn't find a better way to construct the question.
I am trying to validate on server side an array of multipartfile received with other information (e.g email, phone number etc) from an Ajax multipart/form-data post request.
I created 2 custom validator classes by implementing Spring Validator interface, UserFilesValidator to check that all received files are image files and are not above 2MB
and UserInfosValidator to check other user information e.g. phone number validate, email avalibility etc.
I registered the custom validator classes using #InitBinder.
Now when request is made, UserInfosValidator gets called and works fine but UserFilesValidator doesn't get called
Here is my code
Controller class
#Controller
public class UserController{
private final UserFilesValidator userFilesValidator;
private final UserInfosValidator userInfosValidator;
#Autowired
public UserController(UserFilesValidator userFilesValidator, UserInfosValidator userInfosValidator){
this.userFilesValidator = userFilesValidator;
this.userInfosValidator = userInfosValidator;
}
#InitBinder("userfiles")
public void formDataBinder1(WebDataBinder binder){
binder.addValidators(userFilesValidator);
}
#InitBinder("userinfos")
public void formDataBinder2(WebDataBinder binder){
binder.addValidators(userInfosValidator);
}
#RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public ResponseEntity<UserEntity> addUser(#RequestParam("userfiles") #Valid MultipartFile[] files,
#RequestPart("userinfos") #Valid UserEntity user){
//codes ...
}
}
Validator class
#Component
public class UserFilesValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MultipartFile[].class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MultipartFile[] files = (MultipartFile[]) target;
//validation codes ...
}
}
I excluded UserInfosValidator class since it works fine.
I also did not included global exception handler class (that's #ControllerAdvice annotated class)
since errors has to be registered in UserFilesValidator class which is not even getting called.
So please how can i get this to work? I know i can validate it in addUser method but I don't want to have validation codes in controller class
request handler method.

Resources