SpringBoot testing with spring-retry - spring-boot

I had the following function (the function is not really important):
fun myRandomFunc(something: String?): List<Int> {
return listOf(5)
}
And you can imagine it was doing some API calls, returning list of some objects, etc. I could easily mock this function in test like this:
doReturn(
listOf(
5
)
)
.whenever(...).myRandomFunc("something")
But after I introduced (retry/recover) in the mix, that mock is now throwing
org.mockito.exceptions.misusing.NotAMockException at .... Any idea why?
This is the code with spring retry:
#Retryable(
value = [ApiException::class], maxAttempts = MAX_RETRIES,
backoff = Backoff(delay = RETRY_DELAY, multiplier = RETRY_MULTIPLIER, random = true)
)
fun myRandomFunc(something: String?): List<Int> {
return listOf(5)
}
#Recover
fun testMyRandomFunc(exception: Exception): List<Int> {
log.error("Exception occurred ...", exception)
throw RemoteServiceNotAvailableException("Call failed after $MAX_RETRIES retries")
}
The code works, it's functional, just the mocking of tests is now broken. Would appreciate some help

Spring retry creates a proxy around the object.
If there is an interface, the proxy is a JDK proxy; if not, CGLIB is used.
Mockito can't mock CGLIB (final) methods.

Related

How to assign values, call method in Subscribe method in Spring boot web client(non blocking)

I have a Spring boot application. End point A calls three different REST endpoints X, Y, Z. All the calls were using RestTemplate. I am trying to change from RestTemplate to Webclient. As a part of this I changed endpoint Y from RestTemplate to Webclient.
I had a blocking code. It was working as expected. But when I changed it to non-blocking using subscribe things are not working as expected.
With Blocking code
public class SomeImplClass {
#Autowired
private WebClient webClient;
public someReturnType someMethodName()
{
List myList = new ArrayList<>();
Mono<SomeResponse> result = this.webclient.post().uri(url).header(…).bodyValue(….).retrieve().bodyToMone(responseType);
someResponse = result.block(someDuration);
if(someResponse.getId().equals(“000”)
{
myList.addAll(this.somemethod(someResponse));
}else{
log.error(“some error”);
throw new SomeCustomException(“some error”)
}
return myList;
}
With Non Blocking Code
public class SomeImplClass {
#Autowired
private WebClient webClient;
public someReturnType someMethodName()
{
List myList = new ArrayList<>();
Mono<SomeResponse> result = this.webclient.post().uri(url).header(…).bodyValue(….).retrieve().bodyToMone(responseType);
result.subscribe(someResponse -> {
if(someResponse.getId().equals(“000”)
{
myList.addAll(this.somemethod(someResponse));
}
else{
log.error(“some error”);
throw new SomeCustomException(“some error”) //Not able to throw custom exception here.
}
});
return myList;
}
I am getting 2 issues
With non-blocking code the list which I am returning is empty. I guess return is called before subscribe consumes the data. How to resolve this? I tried result.doOnSuccess and doOnNext but both are not working. If I ad d Thread.sleep(5000) before return, everything is working as expected. How to achieve this without adding Thread.sleep.
I am able to throw RunTimeExceptions alone from subscribe. How to throw customeExceptions.

failed to validate request params in a Spring Boot/Kotlin Coroutines controller

In a SpringBoot/Kotlin Coroutines project, I have a controller class like this.
#RestContollser
#Validated
class PostController(private val posts: PostRepository) {
suspend fun search(#RequestParam q:String, #RequestParam #Min(0) offset:Int, #RequestParam #Min(1) limit:Int): ResponseEntity<Any> {}
}
The validation on the #ResquestBody works as the general Spring WebFlux, but when testing
validating request params , it failed and throws an exception like:
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4165)
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below:
It is not a ConstraintViolationException.
I think this is a bug in the framework when you are using coroutines (update , it is, I saw Happy Songs comment). In summary:
"#Validated is indeed not yet Coroutines compliant, we need to fix that by using Coroutines aware methods to discover method parameters."
The trouble is that the signature of the method on your controller is actually enhanced by Spring to have an extra parameter, like this, adding a continuation:
public java.lang.Object com.example.react.PostController.search(java.lang.String,int,int,kotlin.coroutines.Continuation)
so when the hibernate validator calls getParameter names to get the list of parameters on your method, it thinks there are 4 in total on the request, and then gets an index out of bounds exception trying to get the 4th (index 3).
If you put a breakpoint on the return of this:
#Override
public E get(int index) {
return a[index];
}
and put a breakpoint condition of index ==3 && a.length <4 you can see what is going on.
I'd report it as a bug on the Spring issue tracker.
You might be better off taking an alternative approach, as described here, using a RequestBody as a DTO and using the #Valid annotation
https://www.vinsguru.com/spring-webflux-validation/
Thanks for the happy songs' comments, I found the best solution by now to overcome this barrier from the Spring Github issues#23499.
As explained in comments of this issue and PaulNuk's answer, there is a Continuation will be appended to the method arguments at runtime, which will fail the index computation of the method parameter names in the Hibernate Validator.
The solution is changing the ParameterNameDiscoverer.getParameterNames(Method) method and adding a empty string as the additional parameter name when it is a suspend function.
class KotlinCoroutinesLocalValidatorFactoryBean : LocalValidatorFactoryBean() {
override fun getClockProvider(): ClockProvider = DefaultClockProvider.INSTANCE
override fun postProcessConfiguration(configuration: javax.validation.Configuration<*>) {
super.postProcessConfiguration(configuration)
val discoverer = PrioritizedParameterNameDiscoverer()
discoverer.addDiscoverer(SuspendAwareKotlinParameterNameDiscoverer())
discoverer.addDiscoverer(StandardReflectionParameterNameDiscoverer())
discoverer.addDiscoverer(LocalVariableTableParameterNameDiscoverer())
val defaultProvider = configuration.defaultParameterNameProvider
configuration.parameterNameProvider(object : ParameterNameProvider {
override fun getParameterNames(constructor: Constructor<*>): List<String> {
val paramNames: Array<String>? = discoverer.getParameterNames(constructor)
return paramNames?.toList() ?: defaultProvider.getParameterNames(constructor)
}
override fun getParameterNames(method: Method): List<String> {
val paramNames: Array<String>? = discoverer.getParameterNames(method)
return paramNames?.toList() ?: defaultProvider.getParameterNames(method)
}
})
}
}
class SuspendAwareKotlinParameterNameDiscoverer : ParameterNameDiscoverer {
private val defaultProvider = KotlinReflectionParameterNameDiscoverer()
override fun getParameterNames(constructor: Constructor<*>): Array<String>? =
defaultProvider.getParameterNames(constructor)
override fun getParameterNames(method: Method): Array<String>? {
val defaultNames = defaultProvider.getParameterNames(method) ?: return null
val function = method.kotlinFunction
return if (function != null && function.isSuspend) {
defaultNames + ""
} else defaultNames
}
}
Then declare a new validator factory bean.
#Primary
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun defaultValidator(): LocalValidatorFactoryBean {
val factoryBean = KotlinCoroutinesLocalValidatorFactoryBean()
factoryBean.messageInterpolator = MessageInterpolatorFactory().getObject()
return factoryBean
}
Get the complete sample codes from my Github.

How do I properly Unit Test Spring Boot Reactive app with Kotlin & Kotest

I'm fairly new to Kotlin and Kotest, been scratching my head on how to properly unit test webflux application. Looking over kotest samples here, to me that looks like it's spinning up a WebTestClient server and is more like integration test(please correct me if I'm wrong).
My app is fairly simple, I have a rest controller that I'm using constructor injecting to inject my service.
This service uses WebClient to call a different external service that returns a Mono<MyResponse>. And my test looks something like so:
#SpringBootTest
class MyControllerTest : FunSpec({
lateinit var service: MyService
lateinit var controller: MyController
beforeTest {
service = mockk()
controller = MyController(service)
}
test("should return my response") {
val myResponse = MyResponse(name = "John Doe")
every { service.getName(any()) } returns Mono.just(myResponse)
val response = controller.getName()
verify { service.getName(any()) }
response shouldBe myResponse
}
})
The error I'm getting is:
expected: MyResponse(name = "John Doe")
actual: Monojust

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

What is the most likely cause of exceptions mysteriously escaping a try-catch block in this case?

I am using a Spring WebClient in a Kotlin project like this:
data class DTO(val name: String)
#Component
class Runner: ApplicationRunner
{
override fun run(args: ApplicationArguments?)
{
try
{
val dto = get<DTO>()
}
catch (e: Exception)
{
println("ERROR, all exceptions should have been caught in 'get' ")
}
}
}
inline private fun<reified TResult: Any> get(): TResult?
{
var result: TResult? = null
try
{
result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
.get()
.retrieve()
.bodyToMono<TResult>()
.block()
}
catch (e: Exception)
{
println("WORKS AS EXPECTED!!")
}
return result
}
The client will throw an exception, because the API will return a 404. However the exception is not caught where it should be, namely in the body of the get function, but it is propagated to the outer exception handler.
It is interesting to note that this happens only if the exception is thrown by the WebClient. If I replace the code in the try clause with a simple throw Exception("error"), the exception is caught where it should be.
Similarly, when I change the signature of get to a non-generic inline private fun get(): DTO? the problem also goes away.
For an exception to escape the try-catch block seems like a fundamental bug in the Kotlin tools. On the other hand, the fact that this happens only with the WebClient class indicates that this is a Spring problem. Or, it may be just me, using the tools in a wrong way.
I am really baffled here and have no idea how to proceed. Any ideas on why this might be happening are most welcome. Just for completeness, this is what it looks like in the debugger:
EDIT
The issue goes away after upgrading Spring Boot to 2.0.0.M6, it is still present in M5.
So it seems that this was a Spring issue and not a Kotlin issue. On the other hand it would be still nice to understand how a library that you include can seemingly cause the program to violate the laws of the programming language it is written in.
I tried the code with Spring Boot version 2.0.0.M5 and 2.0.0.M6, and it seems the behavior of the following block is different between those 2 versions:
result = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting")
.get()
.retrieve()
.bodyToMono<TResult>()
.block()
somewhere along the chain, on Spring Boot 2.0.0.M5, the WebClientResponseException is returned, on Spring Boot 2.0.0.M6 it is thrown.
If you add a e.printStackTrace() to your outer catch, you will notice that the stack trace is:
java.lang.ClassCastException:
org.springframework.web.reactive.function.client.WebClientResponseException
cannot be cast to com.example.demo.DTO at
com.example.demo.Runner.run(Test.kt:18) at
org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780)
at
org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:770)
at
org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:760)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:328)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)
at
org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)
at com.example.demo.DemoApplicationKt.main(DemoApplication.kt:10)
So, actually, problem is, the returned WebClientResponseException is tried to be cast to DTO class on the moment of return of the call val dto = get<DTO>(). This means that, when you assign result = ..., there is no type checking done yet. So, if you change your code to, for example, call get<Object>() instead of get<DTO>(), it won't hit any catch blocks.
If you convert it to bytecode in IntelliJ Idea, and then decompile it to Java, you can see this block:
public class Runner implements ApplicationRunner {
public void run(#Nullable ApplicationArguments args) {
try {
Object result$iv = null;
try {
ResponseSpec $receiver$iv$iv = WebClient.create("https://maps.googleapis.com/maps/api/nonexisting").get().retrieve();
Mono var10000 = $receiver$iv$iv.bodyToMono((ParameterizedTypeReference)(new Runner$run$$inlined$get$1()));
Intrinsics.checkExpressionValueIsNotNull(var10000, "bodyToMono(object : Para…zedTypeReference<T>() {})");
result$iv = var10000.block();
} catch (Exception var7) {
String var5 = "WORKS AS EXPECTED!!";
System.out.println(var5);
}
DTO var2 = (DTO)result$iv;
} catch (Exception var8) {
String var3 = "ERROR, all exceptions should have been caught in 'get' ";
System.out.println(var3);
}
}
}
Here you can notice that casting to DTO is done on the point of method return (which is not a return anymore because it is inlined), after the inner catch block: DTO var2 = (DTO)result$iv;. It seems like that's the behavior for the inlined methods with reified type parameters.
This is due to SPR-16025 (see related commit) since the Kotlin extension is using internally the ParameterizedTypeReference variant, which has been fixed in Spring Framework 5.0.1, and transitively in Spring Boot 2.0.0.M6.
Note than if you use bodyToMono(TResult::class.java) with Spring Boot 2.0.0.M5, it will works as expected.

Resources