lundi 27 avril 2020

Spring-boot TestRestTemplate calling html error flow, not json error flow

I am trying to use TestRestTemplate to test some error handling for a spring API.

I created a @ControllerAdvice which extends BasicErrorController

I created and override in this controller advice for error() method. This method was being triggered if I did a non-browser plain response, e.g., with curl.

I disabled whitelabel error page with server.error.whitelabel.enabled=false in my applications.properties config. However, for certain errors in the web it was still returning an HTML error page instead of my JSON. In some cases, I JSON response was shown in the browser, but sometimes I still received HTML.

If you explicitly set the accept header like Accept: text/html e.g.,

curl -v -H "Accept: text/html" "http://localhost:8080/foo/bar"

You also receive HTML.

I found that if i also create and override for errorHTML and annotate it like this:

@Override
@RequestMapping(produces = "application/json") 
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) 
{
  return null;
}

It will actual trigger my error() method instead.

Using the above, in the browser or with the above curl I am triggering error(). The reason I care about intercepting the HTML requests is for error/malicious activity tracking - e.g., if an invalid request can set Accept: text/html it will not trigger my logging mechanism which kicks off in a flow for the error() method.

If I remove the errorHTML() method completely from my ControllerAdvice then my tests will in fact go through error(). Something about the existence of the errorHTML() is changing the way TestRestTemplate works and I am not sure where to look.


I am trying to better understand how TestRestTemplate is actually doing the request and why it is not behaving the same as if I were using a browser or curl?

I had done some testing before with MockMvc and found that it doesn't work for this type of testing since it triggers the exception without Springs magic handling. I switched to TestRestTemplate because my understanding is that the testing is more like a real life request to Spring.

I ultimately want to know if this is an edge case I should care about in my app or if this is an odd behavior in testing since I can't think of anyway to get the same result if I request it to my live server from outside of Spring.

Is there another way I can run tests from within Spring that will work the way I want? I understand I could use a shell script running curl or something else exterior, but then I have to manually create something to identify and parse assertions.

Or can I force Spring to ensure error() is called?


Things I've already tried:

Adding attributes to error()

You can see that on errorHTML() I use @RequestMapping(produces = "application/json"). I tried using a few different @RequestMapping() variations such as setting the consumes, headers, etc. and with TestRestTemplate it still always went to errorHTML() which leads me to belive TestRestTemplate does not really act like a real world client.

Throw Exception error not found / no mapping settings

I've seen this advice in a few places:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

But when I did this it did not automatically force things to go to my error() method. I had to add an @ExceptionHandler for the topmost Exception. This got rid of HTML pages, but it also then results in all of my HTTP status codes being 999 because it was no longer using Spring's mappings (as the above setting would imply). This then impacted all of my error handling and responses wouldn't have the proper default error code. It didn't seem to make sense to lose all the defaults or to have to manually re-implement them.

Extending ResponseEntityExceptionHandler

e.g., extends ResponseEntityExceptionHandler

Before extending BasicErrorController I followed some tutorials that suggest instead using ResponseEntityExceptionHandler.

I don't quite understand all the differences, but this was problematic and made it hard for me to centralize error handling and provided no way to utilize getErrorAttributes() from DefaultErrorAttributes. Spring provides the ability to override this class and have one common place for default error attributes returned via JSON - in every example using ResponseEntityExceptionHandler they created a custom POJO per exception instead of starting with a common, unified base. This made no sense to me and to get all the same data would mean reimplementing all the logic to parse request/response already done by getErrorAttributes()


With my current design, everything seems to work in the real world, the problem is in TestRestTemplate

Aucun commentaire:

Enregistrer un commentaire