How to write test cases for custom ErrorAttributes in spring boot - spring-boot

I have updated the spring boot version to 2.6.4 and related other dependencies and got error in getErrorAttributes() method because of changes in its 2nd arguments type from Boolean to ErrorAttributeOptions
Custom ErrorAtttributes class:
#Component
class CustomErrorAttributes<T : Throwable> :DefaultErrorAttributes() {
override fun getErrorAttributes( request: ServerRequest , options: ErrorAttributeOptions ): MutableMap<String, Any> { // changes made here in 2nd parameter
val errorAttributes = super.getErrorAttributes(request, options) // throwing exception here
val status = (errorAttributes as MutableMap<String,Any>).getOrDefault(STATUS_KEY,null)
if(status != null && status as Int == HttpStatus.INTERNAL_SERVER_ERROR.value()){
errorAttributes.replace(MESSAGE_KEY, INTERNAL_SERVER_ERROR_MESSAGE)
}
return errorAttributes
}
}
Test method
private val internalError = "An unexpected error occurred"
#Mock private lateinit var request : ServerRequest
#Test
fun `For Internal Error`(){
var result : MutableMap<String,Any> = customErrorAttributes.getErrorAttributes(request, options) // It was working earlier version as we pass false in 2nd arguments
assertThat(result["message"]).isEqualTo(internalError)
}

Related

How to write unit test for Kotlin delete by id method?

I have this method
fun delete(id: Long) {
NotFoundExceptionValidator(!dishOfTheDayEntityRepository.existsById(id), "dishOfTheDay not found")
dishOfTheDayEntityRepository.deleteById(id)
}
NotFoundExceptionValidator this just checks if it's null then throws error
this is what I tried
#ConcurrentExecution
internal class DishOfTheDayServiceTest {
private val repo: DishOfTheDayEntityRepository = mockk()
private val mapper: DishOfTheDayMapper = mockk()
private val dishOfTheDayEntityService = DishOfTheDayService(repo, mapper)
#Test
fun `delete should work properly`() {
//given
val id: Long = 1;
//when
dishOfTheDayEntityService.delete(1)
//then
verify(exactly = 1) { repo.deleteById(1) }
}
}
when i run it it throws this error
no answer found for: DishOfTheDayEntityRepository(#1).existsById(1)
io.mockk.MockKException: no answer found for: DishOfTheDayEntityRepository(#1).existsById(1)
You forgot to mock your mocks behaviour, i.e. you should explicitly specify what the existsById() and deleteById() methods return. For example for existsById() it should look like:
every { repo.existsById(id) } returns true
I suppose that the deleteById() method returns Unit so if you don't want to do it like above you can mock DishOfTheDayEntityRepository like:
private val repo: DishOfTheDayEntityRepository = mockk(relaxUnitFun = true)
Now you don't have to mock Unit returning methods of DishOfTheDayEntityRepository. You can find more about it here.

Null when injecting mock services, testing a RestController - Mockito

I am testing a REST controller, and I'd like to inject mock service.
But I am getting a null value when calling service Mock
this is my code:
Interface:
interface CaseManagementService {
fun createAccount(caseRequest: CaseRequestDto): Mono<CaseResponseDto>
}
Service:
#Service
class CaseManagementServiceImpl(private val clientManagementService:
ClientManagementService) : CaseManagementService {
override fun createAccount(caseRequest: CaseRequestDto): Mono<CaseResponseDto> {
return clientManagementService.createAccount(caseRequest)
}
}
Controller:
#RestController
#RequestMapping("somepath")
class CaseController(private val caseManagementService: CaseManagementService) {
#PostMapping()
fun createCase(#RequestBody caseRequest: CaseRequestDto): Mono<CaseResponseDto> {
return caseManagementService.createAccount(caseRequest) }
}
The test:
#SpringBootTest
class CaseControllerTests {
#Test
fun `createCase should return case id when a case is created`() {
val caseManagementService: CaseManagementServiceImpl =
Mockito.mock(CaseManagementServiceImpl::class.java)
val caseResponseDtoMono = Mono.just(Fakes().GetFakeCaseResponseDto())
val requestDto = Fakes().GetFakeCaseRequestDto()
`when`(caseManagementService.createAccount(requestDto).thenReturn(caseResponseDtoMono))
var caseController = CaseController(caseManagementService)
//NULL EXCEPTION HAPPENS HERE - RETURNS NULL THIS CALL
var result = caseController.createCase(Fakes().GetFakeCaseRequestDto())
StepVerifier.create(result)
.consumeNextWith { r -> assertEquals(Fakes().GetFakeCaseResponseDto().id, r.id)
}.verifyComplete()
}
}
The closing bracket is in a wrong place: you are calling Mono.thenReturn (on a null Mono instance returned from createAccount) instead of the Mockito's thenReturn (I assume that's what you meant):
`when`(caseManagementService.createAccount(requestDto)).thenReturn(caseResponseDtoMono)
Second problem: you are mocking createAccount call for a specific instance of the CaseRequestDto. In the actual call you are using different instance, so the arguments do not match and the mock returns null. Try reusing the request instance, i.e.:
var result = caseController.createCase(requestDto)
You have mocked the service but not injected the mocked service in the rest controller. That's why you are getting a null pointer. So, caseManagementService needs to be injected in CaseController. Below is a link where you can see the injection part. In the below code I have moved caseController variable above so that caseManagementService is injected in caseControler before it is used.
#SpringBootTest
class CaseControllerTests {
#Test
fun `createCase should return case id when a case is created`() {
val caseManagementService: CaseManagementServiceImpl =
Mockito.mock(CaseManagementServiceImpl::class.java)
var caseController = CaseController(caseManagementService)
val caseResponseDtoMono = Mono.just(Fakes().GetFakeCaseResponseDto())
val requestDto = Fakes().GetFakeCaseRequestDto()
`when`(caseManagementService.createAccount(requestDto).thenReturn(caseResponseDtoMono))
//NULL EXCEPTION HAPPENS HERE - RETURNS NULL THIS CALL
var result = caseController.createCase(Fakes().GetFakeCaseRequestDto())
StepVerifier.create(result)
.consumeNextWith { r -> assertEquals(Fakes().GetFakeCaseResponseDto().id, r.id)
}.verifyComplete()
}
}
https://vmaks.github.io/other/2019/11/04/spring-boot-with-mockito-and-kotlin.html

How to use feign interceptor / decoder to log request - response in custom format?

I'm developing a custom logging framework for springboot to log rest-template requests and response and is working fine. Am trying to implement the same for 'Feign-Client' and am faced with couple of issues.
For request logging, am leveraging FeignRequestInterceptor and it is working fine, only problem here is I cannot retrieve the full request URL.
Below method is giving me only relative URL.
requestTemplate.url()
To log the response, only way i could find was the ResponseDecoder. There I'm able to retrieve everything other than the payload. When accessing the payload from
InputStream is = response.body().asInputStream();
String payload = new String(IOUtils.toByteArray(is));
This method works, but the original stream is closed because of which logging happens fine, but client is throwing exception when returning response.
'trying to open closed stream'
I would like suggestions if there are better ways of logging request response in Feign similar to spring rest-template. Or if the method I have adopted is fine, help me resolve the problems above.
You can configure a custom feign.Logger instance to handle this. There are two built in, JavaLogger which uses java.util.logging and Slf4JLogger that uses slf4j. You can create your own logger implementation by extending feign.Logger and registering it as a #Bean.
That logger should be picked up by Spring and registered with your FeignClient. Here is the Logger base class to get you started:
protected abstract void log(String configKey, String format, Object... args);
Create your own instance, implement this method and it will be called before the request and after the response is returned. No need to update the interceptor or create a response decoder.
in your RestConfiguration you need to up default level of logging feignClient and override by #Bean feignLogger like:
#Configuration(proxyBeanMethods = false)
#EnableCircuitBreaker
#EnableFeignClients(basePackageClasses = [Application::class])
class RestConfiguration: WebMvcConfigurer {
#Bean
fun feignLoggerLevel(): Logger.Level {
return Logger.Level.FULL
}
#Bean
fun feignLogger(): Logger {
return FeignClientLogger()
}
}
and implement your logger (logbook format):
import feign.Logger
import feign.Request
import feign.Response
import feign.Util.*
import org.slf4j.LoggerFactory
class FeignClientLogger : Logger() {
private val log = LoggerFactory.getLogger(this::class.java)
override fun logRequest(configKey: String?, logLevel: Level?, request: Request?) {
if (request == null)
return
val feignRequest = FeignRequest()
feignRequest.method = request.httpMethod().name
feignRequest.url = request.url()
for (field in request.headers().keys) {
for (value in valuesOrEmpty(request.headers(), field)) {
feignRequest.addHeader(field, value)
}
}
if (request.requestBody() != null) {
feignRequest.body = request.requestBody().asString()
}
log.trace(feignRequest.toString())
}
override fun logAndRebufferResponse(
configKey: String?,
logLevel: Level?,
response: Response?,
elapsedTime: Long
): Response? {
if (response == null)
return response
val feignResponse = FeignResponse()
val status = response.status()
feignResponse.status = response.status()
feignResponse.reason =
(if (response.reason() != null && logLevel!! > Level.NONE) " " + response.reason() else "")
feignResponse.duration = elapsedTime
if (logLevel!!.ordinal >= Level.HEADERS.ordinal) {
for (field in response.headers().keys) {
for (value in valuesOrEmpty(response.headers(), field)) {
feignResponse.addHeader(field, value)
}
}
if (response.body() != null && !(status == 204 || status == 205)) {
val bodyData: ByteArray = toByteArray(response.body().asInputStream())
if (logLevel.ordinal >= Level.FULL.ordinal && bodyData.isNotEmpty()) {
feignResponse.body = decodeOrDefault(bodyData, UTF_8, "Binary data")
}
log.trace(feignResponse.toString())
return response.toBuilder().body(bodyData).build()
} else {
log.trace(feignResponse.toString())
}
}
return response
}
override fun log(p0: String?, p1: String?, vararg p2: Any?) {}
}
class FeignResponse {
var status = 0
var reason: String? = null
var duration: Long = 0
private val headers: MutableList<String> = mutableListOf()
var body: String? = null
fun addHeader(key: String?, value: String?) {
headers.add("$key: $value")
}
override fun toString() =
"""{"type":"response","status":"$status","duration":"$duration","headers":$headers,"body":$body,"reason":"$reason"}"""
}
class FeignRequest {
var method: String? = null
var url: String? = null
private val headers: MutableList<String> = mutableListOf()
var body: String? = null
fun addHeader(key: String?, value: String?) {
headers.add("$key: $value")
}
override fun toString() =
"""{"type":"request","method":"$method","url":"$url","headers":$headers,"body":$body}"""
}

Spring 5 Reactive - WebExceptionHandler is not getting called

I have tried all 3 solutions suggested in what is the right way to handle errors in spring-webflux, but WebExceptionHandler is not getting called. I am using Spring Boot 2.0.0.M7. Github repo here
#Configuration
class RoutesConfiguration {
#Autowired
private lateinit var testService: TestService
#Autowired
private lateinit var globalErrorHandler: GlobalErrorHandler
#Bean
fun routerFunction():
RouterFunction<ServerResponse> = router {
("/test").nest {
GET("/") {
ServerResponse.ok().body(testService.test())
}
}
}
}
#Component
class GlobalErrorHandler() : WebExceptionHandler {
companion object {
private val log = LoggerFactory.getLogger(GlobalErrorHandler::class.java)
}
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
log.info("inside handle")
/* Handle different exceptions here */
when(ex!!) {
is ClientException -> exchange!!.response.statusCode = HttpStatus.BAD_REQUEST
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
return Mono.empty()
}
}
UPDATE:
When I change Spring Boot version to 2.0.0.M2, the WebExceptionHandler is getting called. Do I need to do something for 2.0.0.M7?
SOLUTION:
As per Brian's suggestion, it worked as
#Bean
#Order(-2)
fun globalErrorHandler() = GlobalErrorHandler()
You can provide your own WebExceptionHandler, but you have to order it relatively to others, otherwise they might handle the error before yours get a chance to try.
the DefaultErrorWebExceptionHandler provided by Spring Boot for error handling (see reference documentation) is ordered at -1
the ResponseStatusExceptionHandler provided by Spring Framework is ordered at 0
So you can add #Order(-2) on your error handling component, to order it before the existing ones.
An error response should have standard payload info. This can be done by extending AbstractErrorWebExceptionHandler
ErrorResponse: Data Class
data class ErrorResponse(
val timestamp: String,
val path: String,
val status: Int,
val error: String,
val message: String
)
ServerResponseBuilder: 2 different methods to build an error response
default: handle standard errors
webClient: handle webClient exceptions (WebClientResponseException), not for this case
class ServerResponseBuilder(
private val request: ServerRequest,
private val status: HttpStatus) {
fun default(): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
status.value(),
status.name,
status.reasonPhrase)))
fun webClient(e: WebClientResponseException): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
e.statusCode.value(),
e.message.toString(),
e.responseBodyAsString)))
}
GlobalErrorHandlerConfiguration: Error handler
#Configuration
#Order(-2)
class GlobalErrorHandlerConfiguration #Autowired constructor(
errorAttributes: ErrorAttributes,
resourceProperties: ResourceProperties,
applicationContext: ApplicationContext,
viewResolversProvider: ObjectProvider<List<ViewResolver>>,
serverCodecConfigurer: ServerCodecConfigurer) :
AbstractErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
applicationContext
) {
init {
setViewResolvers(viewResolversProvider.getIfAvailable { emptyList() })
setMessageWriters(serverCodecConfigurer.writers)
setMessageReaders(serverCodecConfigurer.readers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> =
RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { response(it, errorAttributes) })
private fun response(request: ServerRequest, errorAttributes: ErrorAttributes?): Mono<ServerResponse> =
ServerResponseBuilder(request, status(request, errorAttributes)).default()
private fun status(request: ServerRequest, errorAttributes: ErrorAttributes?) =
HttpStatus.valueOf(errorAttributesMap(request, errorAttributes)["status"] as Int)
private fun errorAttributesMap(request: ServerRequest, errorAttributes: ErrorAttributes?) =
errorAttributes!!.getErrorAttributes(request, false)
}

Async Spring Boot using Kotlin not working

I'm trying to create a Spring Service that performs an operation asynchronously and returns a ListenableFuture. I want the failure callback to be triggered when the operation fails - my attempt to do this is to use AsyncResult.forExecutionException as seen below:
#Service
open class UserClientService {
#Async
fun fetchUser(email: String): ListenableFuture<User> {
val uri = buildUri(email)
val headers = buildHeaders()
try {
val result = restTemplate.exchange(uri, HttpMethod.GET, HttpEntity<Any>(headers), User::class.java)
return AsyncResult.forValue(result.body)
} catch (e: RestClientException) {
return AsyncResult.forExecutionException(e)
}
}
}
The entry-point:
#SpringBootApplication
#EnableAsync
open class UserProxyApplication
fun main(args: Array<String>) {
SpringApplication.run(UserProxyApplication::class.java, *args)
}
The Spring RestController implementation is as follows:
#RestController
#RequestMapping("/users")
class UserController #Autowired constructor(
val client: UserClientService
) {
#RequestMapping(method = arrayOf(RequestMethod.GET))
fun getUser(#RequestParam(value = "email") email: String): DeferredResult<ResponseEntity<User>> {
val result = DeferredResult<ResponseEntity<User>>(TimeUnit.SECONDS.toMillis(10))
client.fetchUser(email).addCallback(
{ success -> result.setResult(ResponseEntity.ok(success)) },
{ failure -> result.setResult(ResponseEntity(HttpStatus.NOT_FOUND)) }
)
return result;
}
}
Problem is that the failure callback in the UserController is never triggered when an exception is thrown in the UserClientService REST call. Instead, the success callback is triggered with success argument being null.
In Kotlin, I can check if success is null by using success!! - this throws an exception that then does trigger the failure callback with failure argument being the NPE.
Question is how can I trigger the failure callback in the UserController when an exception has occurred in the UserClientService?
Update A it seems that everything is executed on the same thread "http-nio-8080-exec-XXX" regardless of whether I use #Async or not -- see comments.
This all works if:
A) the method fetchUser is declared open, i.e. not final so that Spring can proxy the call
...or...
B) you create an interface IUserClientService and use that in the constructor of the UserController:
interface IUserClientService {
fun fetchUser(email: String): ListenableFuture<User>
}
Now the UserClientService implements the interface:
#Service
open class UserClientService : IUserClientService {
#Async
override fun fetchUser(email: String): ListenableFuture<User> {
// ... rest as shown in question ...
And finally the UserController:
#RestController
#RequestMapping("/users")
class UserController #Autowired constructor(
val client: IUserClientService
) {
#RequestMapping(method = arrayOf(RequestMethod.GET))
fun getUser(#RequestParam(value = "email") email: String): DeferredResult<ResponseEntity<User>> {
// ... rest as shown in question ...
Not sure if this is because I'm using Kotlin. The examples that I've seen don't require implementing an interface.

Resources