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
Related
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.
I have an Enum and I would like to serialize it using custom property. It works in my tests but not when I make request.
Enum should be mapped using JsonValue
enum class PlantProtectionSortColumn(
#get:JsonValue val propertyName: String,
) {
NAME("name"),
REGISTRATION_NUMBER("registrationNumber");
}
In test the lowercase case works as expected.
class PlantProtectionSortColumnTest : ServiceSpec() {
#Autowired
lateinit var mapper: ObjectMapper
data class PlantProtectionSortColumnWrapper(
val sort: PlantProtectionSortColumn,
)
init {
// this works
test("Deserialize PlantProtectionSortColumn enum with custom name ") {
val json = """
{
"sort": "registrationNumber"
}
"""
val result = mapper.readValue(json, PlantProtectionSortColumnWrapper::class.java)
result.sort shouldBe PlantProtectionSortColumn.REGISTRATION_NUMBER
}
// this one fails
test("Deserialize PlantProtectionSortColumn enum with enum name ") {
val json = """
{
"sort": "REGISTRATION_NUMBER"
}
"""
val result = mapper.readValue(json, PlantProtectionSortColumnWrapper::class.java)
result.sort shouldBe PlantProtectionSortColumn.REGISTRATION_NUMBER
}
}
}
But in controller, when i send request with lowercase I get 400. But when the request matches the enum name It works, but response is returned with lowercase. So Spring is not using the objectMapper only for request, in response it is used.
private const val RESOURCE_PATH = "$API_PATH/plant-protection"
#RestController
#RequestMapping(RESOURCE_PATH, produces = [MediaType.APPLICATION_JSON_VALUE])
class PlantProtectionController() {
#GetMapping("/test")
fun get(
#RequestParam sortColumn: PlantProtectionSortColumn,
) = sortColumn
}
I believe kqr's answer is correct and you need to configure converter, not JSON deserializer.
It could look like:
#Component
class StringToPlantProtectionSortColumnConverter : Converter<String, PlantProtectionSortColumn> {
override fun convert(source: String): PlantProtectionSortColumn {
return PlantProtectionSortColumn.values().firstOrNull { it.propertyName == source }
?: throw NotFoundException(PlantProtectionSortColumn::class, source)
}}
In your endpoint you are not parsing json body but query parameters, which are not in json format.
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)
}
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.
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.