Mockito ".thenThrow" throws: "Checked exception is invalid for this method!" - spring

I'm in the process of writing an endpoint and web layer integration test, but I stuck to test the 404 Error.
The Endpoint:
#PreAuthorize("hasPermission('admin.faq')")
override fun getHelpById(helpId: Long): ResponseEntity<HelpDto> {
return try {
val help = helpService.getHelp(helpId)
val helpDto = helpMapper.helpToHelpDto(help)
ResponseEntity.ok(helpDto)
} catch (exception: HelpNotFoundException) {
ResponseEntity.notFound().build()
}
}
The service
override fun getHelp(helpId: Long): Help {
val optionalHelp = helpRepository.findById(helpId)
if (optionalHelp.isEmpty) {
throw HelpNotFoundException(helpId)
}
return optionalHelp.get()
}
The test so far
#Test
fun requestGetHelpByIdWithWrongHelpIdExpect404() {
val uri = "/helps/{helpId}"
val headers = HttpHeaders()
headers.add("X-UserID", "1")
headers.add("X-Permissions", "admin.faq")
val request: HttpEntity<HttpHeaders> = HttpEntity(headers)
val helpId: Long = 1
val params = mapOf("helpId" to helpId)
val builder = UriComponentsBuilder.fromUriString(uri)
val uriWithParams = builder.buildAndExpand(params).toUri().toString()
Mockito.`when`(helpService.getHelp(helpId)).thenThrow(HelpNotFoundException::class.java)
val result = testRestTemplate.exchange(uriWithParams, HttpMethod.GET, request, HelpDto::class.java)
println(result.statusCode)
assert(result != null)
assert(result.statusCode == HttpStatus.NOT_FOUND)
}
With this test I tried to test the case if the helpId cannot be found. To simulate that I mocked the helpService and want the method .getHelp to throw the Exception "HelpNotFound" (I know that mocking isn't so common in Integration Tests, but I had no clue how to solve it without)
My problem:
As mentioned in the title, the call Mockito.`when`(helpService.getHelp(helpId)).thenThrow(HelpNotFoundException::class.java) throws the following ecxeption
Checked exception is invalid for this method!
Invalid: online.minimuenchen.mmos.socialservice.exceptions.HelpNotFoundException
My guesses:
I asked a friend of mine which in pretty good with java, and he said I have to add "throws HelpNotFoundException()" to the Method Signature, but thats not necessary in kotlin right.
So I thought maybe it would help to add the annotation "#Throws(HelpNotFoundException::class)".
Another guess of mine is that "HelpNotFoundException::class.java" should be something like "HelpNotFoundException()".
If I should send more Infos please say it.

My problem was that I put the annotation #Throws() just into the Service Impl and not into the Interface.

Related

Spring wrongfully tries to cast my sealed class into a Collection. Why?

I have a problem with Spring, kotlin, and sealed classes, and don't know where it comes from. It would seem Spring tries to cast my ErrorDto class into a collection, when it shouldn't have to cast it.
Context
I have a sealed class Response that wraps my responses.
sealed interface Response<T>
class ErrorResponse<T>(status: HttpStatus, message: String) : Response<T>,
ResponseEntity<ErrorDto>(ErrorDto(status.value(), message), status)
class SuccessResponse<T>(responseEntity: ResponseEntity<T>) : Response<T>, ResponseEntity<T>(responseEntity.body, responseEntity.headers, responseEntity.statusCode) {
constructor(body: T): this(ok(body))
}
class ErrorDto(val status: Int, #SerializedName("error") val message: String? = null)
All my requests return types are Response<AType>, where AType is a typical response type so that:
we have a ResponseEntity<AType> when the request succeeds,
and when the request encounters an error, it returns a ResponseEntity<ErrorDto>
The wrapper Response is here so that I can correctly type the responses to the REST requests.
Working example
This works correctly with custom types, and here is an example of request that works at runtime:
#GetMapping("/test1")
fun test1(): Response<MyCustomType> {
val response: Response<MyCustomType> = if (true) {
ErrorResponse(HttpStatus.CONFLICT, "test")
} else {
SuccessResponse(MyCustomType())
}
return response
}
The problem
And now, here's an example of what doesn't work at runtime (it compiles just fine):
#GetMapping("/test2")
fun test2(): Response<List<String>> {
val response: Response<List<String>> = if (true) {
ErrorResponse(HttpStatus.CONFLICT, "test")
} else {
SuccessResponse(listOf("toto"))
}
return response
}
The runtime error with this test2 is:
[org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: class ErrorDto cannot be cast to class java.util.Collection
Why?
It would seem that Spring tries to cast the type to a collection when the wrapped response type is a List (Response<List<String>> here), when it shouldn't do that.
Any idea where this problem comes from and why Spring tries to cast to a Collection when it shouldn't do that?
N.B.: As a workaround, I wrapped my List with MyCustomClass in order to go around the problem, but I would still like to understand the why.

How to mock webclient in Kotlin and spring boot for unit tests with mockk framework?

I have the following piece of code in Kotlin (using WebFlux), which I wanna test:
fun checkUser(user: People.User?): Mono<Unit> =
if (user==null) {
Mono.empty()
} else {
webClient.get().uri {
uriBuilder -> uriBuilder
//... building a URI
}.retrieve().bodyToMono(UserValidationResponse::class.java)
.doOnError {
//log something
}.map {
if (!item.isUserValid()) {
throw InvalidUserException()
}
}
}
My unit test so far looks like this:
#Test
fun `Returns error when user is invalid`() {
val user = People.User("name", "lastname", "street", "zip code")
//when
StepVerifier.create(checkUser(user))
//then
.expectError(InvalidUserException::class.java)
.verify()
}
However when I run it, it throw the following error:
io.mockk.MockKException: no answer found for: WebClient(#1).get()
at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:263)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:25)
at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:20)
I guess the error occurs because I havent mocked WebClient(#1).get() but I am not sure how to mock it. So far I have tried:
every { webClient.get() } returns WebClient.RequestHeadersUriSpec
but it doesnt compile. The error says:
Classifier 'RequestHeadersUriSpec' does not have a companion object, and thus must be initialized here
Someone knows how I can mock WebClient(#1).get()? Thanks in advance
Basically you need something like this:
mock ResponseSpec - mock the body or error in whichever way you need for the respective test case
mock RequestHeadersUriSpec - let the retrieve() method return the ResponseSpec mock
mock WebClient - let the get() method return the RequestHeadersUriSpec mock
Here is a full example:
val response = mockk<WebClient.ResponseSpec>()
val spec = mockk<WebClient.RequestHeadersUriSpec<*>>()
val client = mockk<WebClient>()
every { response.bodyToMono(String::class.java) } returns Mono.just("Hello StackOverflow")
every { spec.retrieve() } returns response
every { client.get() } returns spec
println(client.get().retrieve().bodyToMono(String::class.java).block())
This will correctly print the Hello StackOverflow string.
Though it may be a "historical" question, I actually also had this problem recently.
Just as what Krause mentioned, the full call path of WebClient should be mocked. This means the method stream in every{} block should as the same as WebClient call. In your case, it may be something like
every{webClient.get().uri {???}.retrieve().bodyToMono(???)} returns Mono.just(...)
The next question is something about the error message io.mockk.MockKException: no answer found for: RequestBodyUriSpec(#3).uri(......). The key to the question is methods with parameters and without parameters are totally different things.
Thus, for target method, a uri(Function<UriBuilder, URI> uriFunction) is called(a lambda expression is used here to instead of Function interface). However, for mock method, a uri() method without any parameter is called. This is why the error message said , "no answer found for ...". Therefore, in order to match the mocked method, the code should be:
every{webClient.get().uri(any<java.util.function.Function<UriBuilder, URI>>()).retrieve().bodyToMono(???)} returns Mono.just(...)
Or, the any() method can be changed to the real URI which should be as the same as the target method.
Similarly, bodyToMono() should also be mocked with the correct parameter, which may be bodyToMono(any<ParameterizedTypeReference<*>>()).
Finally, the mock code may look like:
every{client.get()
.uri(any<java.util.function.Function<UriBuilder, URI>>())
.retrieve().bodyToMono(any<ParameterizedTypeReference<*>>())}
return Mono.just(...)

How to return single object from Reactive REST API in Kotlin Coroutine style

I am trying to convert a REST service from the Spring 5 Reactive style to an async Kotlin Coroutine style.
I followed several different guides/tutorials on how this should work but I must be doing something wrong.
I get a compile error trying to turn a single object into a Flow, whereas the guides I'm following dont seem to do this at all.
Any pointers or otherwise very appreciated!
Router:
#Bean
fun mainRouter(handler: EobHandler) = coRouter {
GET("/search", handler::search)
GET("/get", handler::get)
}
Handler:
suspend fun search(request: ServerRequest): ServerResponse {
val eobList = service.search()
return ServerResponse.ok().bodyAndAwait(eobList)
}
suspend fun get(request: ServerRequest): ServerResponse
val eob = service.get()
return ServerResponse.ok().bodyAndAwait(eob); // compile error about bodyAndAwait expecting a Flow<T>
}
Service:
override fun search(): Flow<EOB> {
return listOf(EOB()).asFlow()
}
//
override suspend fun get(): EOB? {
return EOB()
}
If curious, here are some of the guides I've based my code on:
https://www.baeldung.com/spring-boot-kotlin-coroutines
https://docs.spring.io/spring/docs/5.2.0.M1/spring-framework-reference/languages.html#how-reactive-translates-to-coroutines
https://medium.com/#hantsy/using-kotlin-coroutines-with-spring-d2784a300bda
I was able to get this to compile by changing
return ServerResponse.ok().bodyAndAwait(eob);
to
return eob?.let { ServerResponse.ok().bodyValueAndAwait(it) } ?: ServerResponse.notFound().buildAndAwait()
guess it's something to do with type-safety of Kotlin - I was not returning a nullable object I think

Multiple requests using WebClient Spring WebFlux

I am trying to make requests using WebClient in parallel, but I have no clue how to go about that,
because no matter what I do, the code is not waiting for requests to finish. If I execute just one request though (Commented fragment), everything works fine. Can someone help me with that?
#RequestMapping(method = [RequestMethod.POST], path = ["/upload/{batchId}"])
fun uploadFile(#RequestPart("file") file: Mono<FilePart>,
#PathVariable("batchId") batchId:String,
#RequestHeader("FILE-SIZE") fileSize:Int): Mono<ServiceResponse> {
val webClient = WebClient.create(commandEndpoint)
// return webClient.put().uri(seriesPath).retrieve().bodyToMono(String::class.java).map { ServiceResponse(it,0) }
return file.map{it.transferTo(Paths.get(storagePath,"excel"))}
.map{excelWorkbookToMetadata(WorkbookFactory.create(Paths.get(storagePath,"excel").toFile()))}
.flatMapMany{Flux.fromIterable(it)}
.flatMap {
it.transactionId = batchId
when (it) {
is SeriesMetadata -> webClient.put().uri(seriesPath,it.id)
.body(BodyInserters.fromObject(it))
.retrieve()
.onStatus({ it == HttpStatus.BAD_REQUEST },{
println("ERROR")
Mono.error(RuntimeException("blah")) }).toMono()
else -> Mono.error(NotImplementedError(""))
}
}
.collectList()
.map {ServiceResponse(batchId, it.size*2) }
}
So it seems, that collectList() filters out empty mono that are returned in case the body of the response is empty. The solution is basically, either to use Mono.defaultIfEmpty() method, or change retrieve() to exchange() which always returns something. At least that's what helped me.

Reactor switchifempty does not behave as expected in junit test

I am writing tests for the method provide below.
`
class ScrapedRecipeCache #Autowired constructor(private val cache: RecipeScrapingCacheService,
private val recipeService: RecipeService) : ScrapedRecipeProvider {
override fun provide(request: ScrapingRequest): Flux<ScrapedRecipe> =
cache.retrieve(request.link)
.doOnNext { println(it) }
.flatMap { (link, _, recipeHash, error) ->
recipeService.findByHash(recipeHash)
.map { ScrapedRecipe(it, link, error)}
.switchIfEmpty(cache.remove(request.link).then(Mono.empty()))
}
.flux()
}
`
The test looks as follows:
private val recipeFetched = Recipe("Tortellini", RecipeDifficulty.EASY, 15.0)
val cacheContents = RecipeScrapingResource("www.google.com", ScrapingOrigin.JAMIE_OLIVER, recipeFetched.hash,
mutableListOf(
pl.goolash.core.Exception("aa", ErrorType.WARNING, LocalDateTime.MIN)
))
val request = ScrapingRequest("www.google.com", ScrapingOrigin.JAMIE_OLIVER, 4)
#BeforeEach
fun setUp() {
given(cache.retrieve("www.google.com")).willReturn(Mono.just(cacheContents))
given(recipeService.findByHash(recipeFetched.hash)).willReturn(Mono.just(recipeFetched))
}
#Test
#DisplayName("Then return data fetched from service and don't delete cache")
fun test() {
cacheFacade.provide(request)
.test()
.expectNext(ScrapedRecipe(recipeFetched, "www.google.com", cacheContents.error!!))
.expectComplete()
.verify()
BDDMockito.verify(cache, BDDMockito.never()).remove(request.link)
}
The test fails because cache.remove(request.link) is called. To my understanding (or from what I managed to gather from documentation) switchIfEmpty, should only be fired when recipeService.findByHash returns Mono.empty(). However the debugger shows that it returns mocked value of Mono.just(fetchedRecipe).
The interesting thing is that when I replace
.switchIfEmpty(cache.remove(request.link).then(Mono.empty()))
with
.switchIfEmpty(Mono.just(1).doOnNext{println("weeee")}.then(Mono.empty()))
Then weee is not printed hence it behaves as expected, that is switchIfEmpty is not fired.
Furthermore the tested issue runs properly in integration test and does not clear the cache.
Reactor version : 3.1.0-RC1
Other notable details: Spring Boot 2.0.0-M4, Mockito-core:2.10, junit 5, project is written in kotlin
The question is, does anybody see anything wrong with this? Because I have spent two days over and still have no clue why this behaves so bizzarely.
Finally I found out how to make this work.
In order to remedy it:
override fun provide(request: ScrapingRequest): Flux<ScrapedRecipe> =
cache.retrieve(request.link)
.flatMap { (link, _, recipeHash, error) ->
recipeService.findByHash(recipeHash)
.map { ScrapedRecipe(it, link, error) }
.switchIfEmpty(Mono.just(1)
.flatMap { cache.remove(request.link) }
.then(Mono.empty()))
}
.flux()
You can see how using flatMap to execute the asynch work does the job, even if this is not the neatest implementation, it revealed to me quite an interesting mechanism hidden here.

Resources