I have a Spring boot controller with a method like this:
// CREATE
#RequestMapping(method=RequestMethod.POST, value="/accounts")
public ResponseEntity<Account> createAccount(#RequestBody Account account,
#RequestHeader(value = "Authorization") String authorizationHeader,
UriComponentsBuilder uriBuilder) {
if (!account.getEmail().equalsIgnoreCase("")) {
account = accountService.createAccount(account);
HttpHeaders headers = new HttpHeaders();
System.out.println( "Account is null = " + (null == account)); //For debugging
headers.setLocation(uriBuilder.path("/accounts/{id}").buildAndExpand(account.getId()).toUri());
return new ResponseEntity<>(account, headers, HttpStatus.CREATED);
}
return new ResponseEntity<>(null, null, HttpStatus.BAD_REQUEST);
}
I'm trying to unit test it like this:
#Test
public void givenValidAccount_whenCreateAccount_thenSuccessed() throws Exception {
/// Arrange
AccountService accountService = mock(AccountService.class);
UriComponentsBuilder uriBuilder = mock(UriComponentsBuilder.class);
AccountController accountController = new AccountController(accountService);
Account account = new Account("someone#domain.com");
/// Act
ResponseEntity<?> createdAccount = accountController.createAccount(account, "", uriBuilder);
/// Assert
assertNotNull(createdAccount);
//assertEquals(HttpStatus.CREATED, createdAccount.getStatusCode());
}
but the account is always null. Any idea why is that ?
You may want to check my answer in How to test this method with spring boot test?
Not only you will find an answer to unit testing controllers but also how to include filters, handlers and interceptors in your test.
Hope this helps,
Jake
I think you need to when clause first of all.
when(accountController.createAccount(account, "", uriBuilder)).then(createAccount);
ResponseEntity<?> createdAccount = accountController.createAccount(account, "", uriBuilder);
Related
I have a spring boot application that has the below AuthFilter added for all rest apis exposed by the application. I want to test the below code that validates authorization token by calling a third party api call. I tried Mockito but how do I inject the mocked HttpPost, HttpClient etc object in the filter class?
Also what value do I pass to thirdPartyAPIUrl property which is configured in application.properties for test class
#Component
public class AuthTokenFilter implements Filter {
public boolean isAuthTokenValid(HttpServletRequest request, HttpServletResponse response) throws IOException {
String authorizationToken = request.getHeader(RequestHeaders.AUTHORIZATION.toString());
TokenRequest validateTokenRequest = new TokenRequest();
validateTokenRequest.setToken(authorizationToken);
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(this.thirdPartyAPIUrl); //fetched through application.properties
httpPost.setHeader("Content-type", "application/json");
StringEntity requestBody = new StringEntity(new Gson().toJson(validateTokenRequest));
httpPost.setEntity(requestBody);
try (CloseableHttpResponse validateTokenResponse = httpclient.execute(httpPost)) {
HttpEntity rEntity = validateTokenResponse.getEntity();
TokenResponse tokenResponse = new ObjectMapper().readValue(rEntity.getContent(),
TokenResponse.class);
logger.debug("API Response Object : {}", tokenResponse);
}
}
return false; //temporary
}
}
Thanks!
I would recommend avoiding mocking HttpPost etc and instead just mocking the third-party server. My preferred tool to use for this is wiremock
Here is an example of how it would be used:
(make sure to import this for options, caused me a lot of headaches ;) )
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
... code
static WireMockServer wireMockServer = new WireMockServer(options().port(8080));
#BeforeAll
static void init() {
wireMockServer.start();
}
//this is for the case that you have multiple test suites that mock the server, to avoid conflicts with ports
#AfterAll
static void releaseResource() {
wireMockServer.stop();
}
#Test
void test() {
wireMockServer.stubFor(post("/endpoint").willReturn(aResponse().withStatus(200)));
... more code
filter.isAuthTokenValid(request, response);
}
I have an folowing endpoint:
#PostMapping(value = "/home", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public Mono<String> getData(ServerWebExchange exchange) { return Mono.empty(); }
The ServerWebExchange object is implemented in org.springframework.web.server.
When I run it, in Swagger all the getters objects are shown. While I only need the body (I want to hide the reqest and the respone objects).
Tried to use
.ignoredParameterTypes(Principal.class, ServerHttpRequest.class, ServerHttpResponse.class)
But, it didn't had any effect.
Is there a way to hide those?
Solution found:
Desable the SeverWebExchange interface for swagger
Configure requier input.
`
#PostMapping(value = "/home", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
#ApiImplicitParams({
#ApiImplicitParam(name = "Body Params", paramType = "body")
})
public Mono<String> getData(
#ApiIgnore ServerWebExchange exchange
) {
return Mono.empty();
}
I've written a typical spring boot application, now I want to add integration tests to that application.
I've got the following controller and test:
Controller:
#RestController
public class PictureController {
#RequestMapping(value = "/uploadpicture", method = RequestMethod.POST)
public ResponseEntity<VehicleRegistrationData> uploadPicturePost(#RequestPart("userId") String userId, #RequestPart("file") MultipartFile file) {
try {
return ResponseEntity.ok(sPicture.saveAndParsePicture(userId, file));
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
Test:
#Test
public void authorizedGetRequest() throws Exception {
File data = ResourceUtils.getFile(testImageResource);
byte[] bytes = FileUtils.readFileToByteArray(data);
ObjectMapper objectMapper = new ObjectMapper();
MockMultipartFile file = new MockMultipartFile("file", "test.jpg", MediaType.IMAGE_JPEG_VALUE, bytes);
MockMultipartFile userId =
new MockMultipartFile("userId",
"userId",
MediaType.MULTIPART_FORM_DATA_VALUE,
objectMapper.writeValueAsString("123456").getBytes()
);
this.mockMvc.perform(multipart("/uploadPicture")
.file(userId)
.file(file)
.header(API_KEY_HEADER, API_KEY)).andExpect(status().isOk());
}
Testing the controller with the OkHttp3 client on android works seamlessly, but I can't figure out how to make that request work on the MockMvc
I expect 200 as a status code, but get 404 since, I guess, the format is not the correct one for that controller
What am I doing wrong?
It must be a typo.
In your controller, you claim the request URL to be /uploadpicture, but you visit /uploadPicture for unit test.
I have controller:
#PostMapping(value = "/value/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String updateSettings(final Dto dto) {
System.out.println(">>> " + dto);
return "template";
}
Controller works if I send request across chrome window. But when I write test for this method I get problem. Not converted object, value not inserted.
Test:
#Test
#WithMockUser(username = FAKE_VALID_USER, password = FAKE_VALID_PASSWORD)
public void test_B_CreateDtoWithValidForm() throws Exception {
final Dto dto = new Dto();
dto.setId("value");
dto.setEnabled("true");
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content(dto.toString()))
.andDo(print());
}
Output is >>> Dto{id=null, enabled=false}
How test Post request with custom object in content type application/x-www-form-urlencoded?
In this case you don't need to use content, but instead you need to use param in this way:
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.param("id", "value")
.param("enabled", "true"))
.andDo(print());
I am working with Spring Framework 4.0.7, together with MVC and Rest
I can work in peace with:
#Controller
ResponseEntity<T>
For example:
#Controller
#RequestMapping("/person")
#Profile("responseentity")
public class PersonRestResponseEntityController {
With the method (just to create)
#RequestMapping(value="/", method=RequestMethod.POST)
public ResponseEntity<Void> createPerson(#RequestBody Person person, UriComponentsBuilder ucb){
logger.info("PersonRestResponseEntityController - createPerson");
if(person==null)
logger.error("person is null!!!");
else
logger.info("{}", person.toString());
personMapRepository.savePerson(person);
HttpHeaders headers = new HttpHeaders();
headers.add("1", "uno");
//http://localhost:8080/spring-utility/person/1
headers.setLocation(ucb.path("/person/{id}").buildAndExpand(person.getId()).toUri());
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
to return something
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Person> getPerson(#PathVariable Integer id){
logger.info("PersonRestResponseEntityController - getPerson - id: {}", id);
Person person = personMapRepository.findPerson(id);
return new ResponseEntity<>(person, HttpStatus.FOUND);
}
Works fine
I can do the same with:
#RestController (I know it is the same than #Controller + #ResponseBody)
#ResponseStatus
For example:
#RestController
#RequestMapping("/person")
#Profile("restcontroller")
public class PersonRestController {
With the method (just to create)
#RequestMapping(value="/", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void createPerson(#RequestBody Person person, HttpServletRequest request, HttpServletResponse response){
logger.info("PersonRestController - createPerson");
if(person==null)
logger.error("person is null!!!");
else
logger.info("{}", person.toString());
personMapRepository.savePerson(person);
response.setHeader("1", "uno");
//http://localhost:8080/spring-utility/person/1
response.setHeader("Location", request.getRequestURL().append(person.getId()).toString());
}
to return something
#RequestMapping(value="/{id}", method=RequestMethod.GET)
#ResponseStatus(HttpStatus.FOUND)
public Person getPerson(#PathVariable Integer id){
logger.info("PersonRestController - getPerson - id: {}", id);
Person person = personMapRepository.findPerson(id);
return person;
}
My questions are:
when for a solid reason or specific scenario one option must be used mandatorily over the other
If (1) does not matter, what approach is suggested and why.
ResponseEntity is meant to represent the entire HTTP response. You can control anything that goes into it: status code, headers, and body.
#ResponseBody is a marker for the HTTP response body and #ResponseStatus declares the status code of the HTTP response.
#ResponseStatus isn't very flexible. It marks the entire method so you have to be sure that your handler method will always behave the same way. And you still can't set the headers. You'd need the HttpServletResponse.
Basically, ResponseEntity lets you do more.
To complete the answer from Sotorios Delimanolis.
It's true that ResponseEntity gives you more flexibility but in most cases you won't need it and you'll end up with these ResponseEntity everywhere in your controller thus making it difficult to read and understand.
If you want to handle special cases like errors (Not Found, Conflict, etc.), you can add a HandlerExceptionResolver to your Spring configuration. So in your code, you just throw a specific exception (NotFoundException for instance) and decide what to do in your Handler (setting the HTTP status to 404), making the Controller code more clear.
According to official documentation: Creating REST Controllers with the #RestController annotation
#RestController is a stereotype annotation that combines #ResponseBody
and #Controller. More than that, it gives more meaning to your
Controller and also may carry additional semantics in future releases
of the framework.
It seems that it's best to use #RestController for clarity, but you can also combine it with ResponseEntity for flexibility when needed (According to official tutorial and the code here and my question to confirm that).
For example:
#RestController
public class MyController {
#GetMapping(path = "/test")
#ResponseStatus(HttpStatus.OK)
public User test() {
User user = new User();
user.setName("Name 1");
return user;
}
}
is the same as:
#RestController
public class MyController {
#GetMapping(path = "/test")
public ResponseEntity<User> test() {
User user = new User();
user.setName("Name 1");
HttpHeaders responseHeaders = new HttpHeaders();
// ...
return new ResponseEntity<>(user, responseHeaders, HttpStatus.OK);
}
}
This way, you can define ResponseEntity only when needed.
Update
You can use this:
return ResponseEntity.ok().headers(responseHeaders).body(user);
A proper REST API should have below components in response
Status Code
Response Body
Location to the resource which was altered(for example, if a resource was created, client would be interested to know the url of that location)
The main purpose of ResponseEntity was to provide the option 3, rest options could be achieved without ResponseEntity.
So if you want to provide the location of resource then using ResponseEntity would be better else it can be avoided.
Consider an example where a API is modified to provide all the options mentioned
// Step 1 - Without any options provided
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public #ResponseBody Spittle spittleById(#PathVariable long id) {
return spittleRepository.findOne(id);
}
// Step 2- We need to handle exception scenarios, as step 1 only caters happy path.
#ExceptionHandler(SpittleNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
long spittleId = e.getSpittleId();
return new Error(4, "Spittle [" + spittleId + "] not found");
}
// Step 3 - Now we will alter the service method, **if you want to provide location**
#RequestMapping(
method=RequestMethod.POST
consumes="application/json")
public ResponseEntity<Spittle> saveSpittle(
#RequestBody Spittle spittle,
UriComponentsBuilder ucb) {
Spittle spittle = spittleRepository.save(spittle);
HttpHeaders headers = new HttpHeaders();
URI locationUri =
ucb.path("/spittles/")
.path(String.valueOf(spittle.getId()))
.build()
.toUri();
headers.setLocation(locationUri);
ResponseEntity<Spittle> responseEntity =
new ResponseEntity<Spittle>(
spittle, headers, HttpStatus.CREATED)
return responseEntity;
}
// Step4 - If you are not interested to provide the url location, you can omit ResponseEntity and go with
#RequestMapping(
method=RequestMethod.POST
consumes="application/json")
#ResponseStatus(HttpStatus.CREATED)
public Spittle saveSpittle(#RequestBody Spittle spittle) {
return spittleRepository.save(spittle);
}