MockMvc GET request failed with 404 but the URL is valid - spring

I am trying to test my controller using MockMvc and the springmockk library, when I make a request for a valid URL, I get a 404 error.
Here is the BadgeControllerImplTest:
package uno.d1s.pulseq.controller
import com.ninjasquad.springmockk.MockkBean
import io.mockk.every
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import uno.d1s.pulseq.controller.impl.BadgeControllerImpl
import uno.d1s.pulseq.core.constant.mapping.BadgeMappingConstants
import uno.d1s.pulseq.service.BadgeService
#WebMvcTest(useDefaultFilters = false, controllers = [BadgeControllerImpl::class])
class BadgeControllerImplTest {
#Autowired
private lateinit var mockMvc: MockMvc
#MockkBean
private lateinit var badgeService: BadgeService
#BeforeEach
fun setup() {
every {
badgeService.createBadge(any(), any(), any(), any(), any())
}.returns(byteArrayOf())
}
#Test
fun `should return the badge on getBadge`() {
mockMvc.get(BadgeMappingConstants.GET_BADGE.replace("{statisticId}", "total-beats")) {
param("color", "red")
param("title", "Redefined title")
}.andExpect {
status {
isOk()
}
}
}
}
This is the code I'm trying to test:
package uno.d1s.pulseq.controller
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import uno.d1s.pulseq.core.constant.mapping.BadgeMappingConstants
import javax.servlet.http.HttpServletResponse
import javax.validation.constraints.NotEmpty
interface BadgeController {
#RequestMapping(
BadgeMappingConstants.GET_BADGE,
method = [RequestMethod.GET]
)
fun getBadge(
#PathVariable statisticId: String,
#RequestParam(required = false) color: String?,
#RequestParam(required = false) title: String?,
#RequestParam(required = false) style: String?,
#RequestParam(required = false) logoUrl: String?,
response: HttpServletResponse
)
}
The implementation of this interface is:
package uno.d1s.pulseq.controller.impl
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.RestController
import uno.d1s.pulseq.controller.BadgeController
import uno.d1s.pulseq.service.BadgeService
import javax.servlet.http.HttpServletResponse
import javax.validation.constraints.NotEmpty
#Validated
#RestController
class BadgeControllerImpl : BadgeController {
#Autowired
private lateinit var badgeService: BadgeService
override fun getBadge(
#NotEmpty statisticId: String,
color: String?,
title: String?,
style: String?,
logoUrl: String?,
response: HttpServletResponse
) {
response.run {
contentType = "image/svg+xml"
val badge = badgeService.createBadge(statisticId, color, title, style, logoUrl)
writer.println(String(badge))
}
}
}
The controller maps at /api/badge/{statisticId}
Thank you.

I solved this problem by marking BadgeControllerImplTest with #ContextConfiguration(classes = [BadgeControllerImpl::class])

Related

#Autowired not working inside testcontainers

I am using test containers to make integration tests with the MSSQLServer image and when I am trying to inject my repository I am recieving the following error:
lateinit property modelRepository has not been initialized
And this confuse me because I'm using the same path I used to create the test but using a postgress database in another project.
My test archive:
package *.models.integration
import *.portadapter.repository.ModelRepository
import *.builders.ModelBuilder
import org.junit.ClassRule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.MSSQLServerContainer
import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy
import org.testcontainers.junit.jupiter.Testcontainers
import java.time.Duration
import java.util.function.Supplier
#DataJpaTest
#Testcontainers
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ModelRepositoryTest {
#Autowired
private lateinit var modelRepository: ModelRepository
companion object {
private val hibernateDriver: Supplier<Any> = Supplier { "org.hibernate.dialect.SQLServerDialect" }
private val hibernateDll: Supplier<Any> = Supplier { "none" }
#ClassRule
#JvmField
val mssqlserver: MSSQLServerContainer<*> = MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-latest")
.withStartupCheckStrategy(MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5)))
.acceptLicense()
#JvmStatic
#DynamicPropertySource
fun properties(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url", mssqlserver::getJdbcUrl)
registry.add("spring.datasource.username", mssqlserver::getUsername)
registry.add("spring.datasource.password", mssqlserver::getPassword)
registry.add("spring.jpa.properties.hibernate.dialect", hibernateDriver)
registry.add("spring.jpa.hibernate.ddl-auto", hibernateDll)
mssqlserver.acceptLicense().start()
}
}
#Test
fun `it should save a model`() {
val model = ModelBuilder().createModel().get()
modelRepository.save(model)
val modelFound = modelRepository.findByCode(model.code)
assertEquals(model.code, modelFound.get().code)
}
}
My Repository:
package *.portadapter.repository
import *.model.domain.Model
import org.springframework.data.jpa.repository.JpaRepository
import java.util.*
interface ModelRepository: JpaRepository<Model, String> {
fun findByCode(code: String): Optional<Model>
}
Could anyone please help me
Make sure you are using the right classes from junit4 or junit5. If you are using junit4 then add #RunWith(SpringRunner.class)
As I mentioned in the comment above, imports from junit4 and junit5 are mixed in the test provided. Below you can find the examples
junit4
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.testcontainers.containers.MSSQLServerContainer;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
#RunWith(SpringRunner.class)
#DataJdbcTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class SqlserverDemoApplication3Tests {
#ClassRule
public static MSSQLServerContainer sqlserver = new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-CU12")
.acceptLicense();
#DynamicPropertySource
static void sqlserverProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", sqlserver::getJdbcUrl);
registry.add("spring.datasource.username", sqlserver::getUsername);
registry.add("spring.datasource.password", sqlserver::getPassword);
}
#Autowired
private JdbcTemplate jdbcTemplate;
#Test
public void contextLoads() {
this.jdbcTemplate.update("insert into profile (name) values ('profile-1')");
List<Map<String, Object>> records = this.jdbcTemplate.queryForList("select * from profile");
assertThat(records).hasSize(1);
}
}
junit5
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
#Testcontainers
#DataJdbcTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class SqlserverDemoApplication2Tests {
#Container
private static MSSQLServerContainer sqlserver = new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-CU12")
.acceptLicense();
#DynamicPropertySource
static void sqlserverProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", sqlserver::getJdbcUrl);
registry.add("spring.datasource.username", sqlserver::getUsername);
registry.add("spring.datasource.password", sqlserver::getPassword);
}
#Autowired
private JdbcTemplate jdbcTemplate;
#Test
void contextLoads() {
this.jdbcTemplate.update("insert into profile (name) values ('profile-1')");
List<Map<String, Object>> records = this.jdbcTemplate.queryForList("select * from profile");
assertThat(records).hasSize(1);
}
}

How to fix .UnsatisfiedDependencyException error in Spring?

I have the following code:
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
#RestController
class MessageResource(var service: MessageService) {
#GetMapping
fun index(): List<Message> = service.findMessagesInDB()
#PostMapping
fun post(#RequestBody message: Message) {
service.addMessageToDB(message)
}
}
#Service
class MessageService(val db : MessageRepository) {
fun findMessagesInDB(): List<Message> = db.findMessages()
fun addMessageToDB(message: Message){
db.save(message)
}
}
#Repository
interface MessageRepository : CrudRepository<Message, String> {
#Query("select * from messages")
fun findMessages(): List<Message>
}
#Table(name ="MESSAGES")
#Entity
data class Message(#Id val id: String?, val text: String)
I get
java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List com.example.demo.MessageRepository.findMessages()!
I followed this kotlin-Spring tutorial: https://kotlinlang.org/docs/jvm-spring-boot-restful.html#add-database-support
It seems to me that a nativeQuery = true attribute is missing in your #Query. Try the following:
#Repository
interface MessageRepository : CrudRepository<Message, String> {
#Query(value = "select * from messages", nativeQuery = true)
fun findMessages(): List<Message>
}
Additionally, this custom #Query method seems unnecessary, since CrudRepository has a method (findAll()) that does exactly that.

JWT Authorization Kotlin Coroutines Spring Security and Spring WebFlux

I'm trying to implement Authentication & Authorization with Spring Boot (2.3.1.RELEASE) WebFlux + Kotlin + Coroutines, I think that I reach a point where I cannot find any useful information about Authorization even in the Spring Security github source code.
If I understand correctly, for the Authentication flow I did the following:
#Bean
fun authenticationWebFilter(reactiveAuthenticationManager: ReactiveAuthenticationManager,
jwtConverter: JWTConverter,
serverAuthenticationSuccessHandler: ServerAuthenticationSuccessHandler): AuthenticationWebFilter {
val authenticationWebFilter = AuthenticationWebFilter(reactiveAuthenticationManager)
authenticationWebFilter.setRequiresAuthenticationMatcher { ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login").matches(it) }
authenticationWebFilter.setServerAuthenticationConverter(jwtConverter)
authenticationWebFilter.setAuthenticationSuccessHandler(serverAuthenticationSuccessHandler)
return authenticationWebFilter
}
The flow is:
ServerWebExchangeMatcher is executed and check if the client is requesting the login endpoint with the correct HTTP verb.
If everything is ok, the ServerAuthenticationConverter gets the username and password from the body and creates a unauthenticated UsernamePasswordAuthenticationToken.
ReactiveAuthenticationManager is called and performs the authentication (looks in db and check passwords).
If everything goes well the ServerAuthenticationSuccessHandler is called.
My ServerAuthenticationConverter:
import com.kemenu.dark.admin.application.HttpExceptionFactory.badRequest
import com.kemenu.dark.admin.application.login.LoginRequest
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactor.mono
import org.springframework.core.ResolvableType
import org.springframework.http.MediaType
import org.springframework.http.codec.json.AbstractJackson2Decoder
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import javax.validation.Validator
#Component
class JWTConverter(private val jacksonDecoder: AbstractJackson2Decoder,
private val validator: Validator) : ServerAuthenticationConverter {
override fun convert(exchange: ServerWebExchange?): Mono<Authentication> = mono {
val loginRequest = getUsernameAndPassword(exchange!!) ?: throw badRequest()
if (validator.validate(loginRequest).isNotEmpty()) {
throw badRequest()
}
return#mono UsernamePasswordAuthenticationToken(loginRequest.username, loginRequest.password)
}
private suspend fun getUsernameAndPassword(exchange: ServerWebExchange): LoginRequest? {
val dataBuffer = exchange.request.body
val type = ResolvableType.forClass(LoginRequest::class.java)
return jacksonDecoder
.decodeToMono(dataBuffer, type, MediaType.APPLICATION_JSON, mapOf())
.onErrorResume { Mono.empty<LoginRequest>() }
.cast(LoginRequest::class.java)
.awaitFirstOrNull()
}
}
My ReactiveAuthenticationManager:
#Bean
fun reactiveAuthenticationManager(reactiveUserDetailsService: AdminReactiveUserDetailsService,
passwordEncoder: PasswordEncoder): ReactiveAuthenticationManager {
val manager = UserDetailsRepositoryReactiveAuthenticationManager(reactiveUserDetailsService)
manager.setPasswordEncoder(passwordEncoder)
return manager
}
My ServerAuthenticationSuccessHandler:
import com.kemenu.dark.admin.application.HttpExceptionFactory.unauthorized
import com.kemenu.dark.admin.application.security.JWTService
import kotlinx.coroutines.reactor.mono
import org.springframework.security.core.Authentication
import org.springframework.security.core.userdetails.User
import org.springframework.security.web.server.WebFilterExchange
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
#Component
class JWTServerAuthenticationSuccessHandler(private val jwtService: JWTService) : ServerAuthenticationSuccessHandler {
private val FIFTEEN_MIN = 1000 * 60 * 15
private val FOUR_HOURS = 1000 * 60 * 60 * 4
override fun onAuthenticationSuccess(webFilterExchange: WebFilterExchange?, authentication: Authentication?): Mono<Void> = mono {
val principal = authentication?.principal ?: throw unauthorized()
when(principal) {
is User -> {
val accessToken = jwtService.accessToken(principal.username, FIFTEEN_MIN)
val refreshToken = jwtService.refreshToken(principal.username, FOUR_HOURS)
webFilterExchange?.exchange?.response?.headers?.set("Authorization", accessToken)
webFilterExchange?.exchange?.response?.headers?.set("JWT-Refresh-Token", refreshToken)
}
}
return#mono null
}
}
My Security config:
import com.kemenu.dark.admin.application.security.authentication.AdminReactiveUserDetailsService
import com.kemenu.dark.admin.application.security.authentication.JWTConverter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.http.codec.json.AbstractJackson2Decoder
import org.springframework.http.codec.json.Jackson2JsonDecoder
import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager
import org.springframework.security.authorization.ReactiveAuthorizationManager
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.AuthenticationWebFilter
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.security.web.server.authorization.AuthorizationWebFilter
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.reactive.config.WebFluxConfigurer
import org.springframework.web.reactive.function.server.router
import org.springframework.web.server.ServerWebExchange
import java.net.URI
#Configuration
#EnableWebFlux
#EnableWebFluxSecurity
class WebConfig : WebFluxConfigurer {
#Bean
fun configureSecurity(http: ServerHttpSecurity,
jwtAuthenticationFilter: AuthenticationWebFilter,
jwtAuthorizationWebFilter: AuthorizationWebFilter): SecurityWebFilterChain {
return http
.cors().disable()
.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.authorizeExchange()
.pathMatchers("/login").permitAll()
.anyExchange().authenticated()
.and()
.addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterAt(jwtAuthorizationWebFilter, SecurityWebFiltersOrder.AUTHORIZATION)
.build()
}
#Bean
fun mainRouter() = router {
accept(MediaType.TEXT_HTML).nest {
GET("/") { temporaryRedirect(URI("/index.html")).build() }
}
resources("/**", ClassPathResource("public/"))
}
#Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
#Bean
fun authenticationWebFilter(reactiveAuthenticationManager: ReactiveAuthenticationManager,
jwtConverter: JWTConverter,
serverAuthenticationSuccessHandler: ServerAuthenticationSuccessHandler): AuthenticationWebFilter {
val authenticationWebFilter = AuthenticationWebFilter(reactiveAuthenticationManager)
authenticationWebFilter.setRequiresAuthenticationMatcher { ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login").matches(it) }
authenticationWebFilter.setServerAuthenticationConverter(jwtConverter)
authenticationWebFilter.setAuthenticationSuccessHandler(serverAuthenticationSuccessHandler)
return authenticationWebFilter
}
#Bean
fun authorizationWebFilter(jwtReactiveAuthorizationManager: ReactiveAuthorizationManager<ServerWebExchange>): AuthorizationWebFilter = AuthorizationWebFilter(jwtReactiveAuthorizationManager)
#Bean
fun jacksonDecoder(): AbstractJackson2Decoder = Jackson2JsonDecoder()
#Bean
fun reactiveAuthenticationManager(reactiveUserDetailsService: AdminReactiveUserDetailsService,
passwordEncoder: PasswordEncoder): ReactiveAuthenticationManager {
val manager = UserDetailsRepositoryReactiveAuthenticationManager(reactiveUserDetailsService)
manager.setPasswordEncoder(passwordEncoder)
return manager
}
}
Now for the Authorization I created a ReactiveAuthorizationManager:
import com.kemenu.dark.admin.application.security.JWTService
import kotlinx.coroutines.reactor.mono
import org.springframework.http.HttpHeaders
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authorization.AuthorizationDecision
import org.springframework.security.authorization.ReactiveAuthorizationManager
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
#Component
class JWTReactiveAuthorizationManager(private val jwtService: JWTService) : ReactiveAuthorizationManager<ServerWebExchange> {
override fun check(authentication: Mono<Authentication>?, exchange: ServerWebExchange?): Mono<AuthorizationDecision> = mono {
val authHeader = exchange?.request?.headers?.getFirst(HttpHeaders.AUTHORIZATION) ?: return#mono AuthorizationDecision(false)
if (!authHeader.startsWith("Bearer ")) {
return#mono AuthorizationDecision(false)
}
val decodedJWT = jwtService.decodeAccessToken(authHeader)
if (decodedJWT.subject.isNullOrBlank()) {
return#mono AuthorizationDecision(false)
}
SecurityContextHolder.getContext().authentication = UsernamePasswordAuthenticationToken(decodedJWT.subject, null, listOf())
return#mono AuthorizationDecision(true)
}
}
And I added it to the configuration as a WebFilter for Authorization.
So far so good, my problem comes when I run this test:
#Test
fun `Given an admin when tries to fetch data from customers API with AUTHORIZATION then receives the data`() {
val customer = CustomerHelper.random()
runBlocking {
customerRepository.save(customer)
}
webTestClient
.get().uri("/v1/customers")
.header(HttpHeaders.AUTHORIZATION, accessToken())
.exchange()
.expectStatus().isOk
.expectBodyList<Customer>()
.contains(customer)
}
Then I receive an unauthorized 401 http error, why?

Kotlin + SpringBootTest + Junit 5 + AutoConfigureMockMvc: test passing when it was supposed to fail (seems #BeforeEach not taking effect)

I coded a very simple and common CRUD in Kotlin. I want to do basic tests as testing post, delete, get and put.
Probably I understood something wrong: I used Beforeeach aimed to insert a register so I could check during get test. I don't get exception but it seems during get test it always returning ok when it should be NOT_FOUND for any other id different than 1 in bellow test.
Any clue or guidance in right direction will be wellcome even if see other bad practice bellow based on my purpose (simple CRUD test).
test
package com.mycomp.jokenpo
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.mycomp.jokenpo.controller.UserController
import com.mycomp.jokenpo.model.User
import com.mycomp.jokenpo.respository.UserRepository
import com.mycomp.jokenpo.service.UserService
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultHandlers
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.test.web.servlet.setup.MockMvcBuilders
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension::class)
#AutoConfigureMockMvc
class JokenpoApplicationTests {
#Autowired
lateinit var testRestTemplate: TestRestTemplate
#Autowired
private lateinit var mvc: MockMvc
#InjectMocks
lateinit var controller: UserController
#Mock
lateinit var respository: UserRepository
#Mock
lateinit var service: UserService
//private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
#BeforeEach
fun setup() {
MockitoAnnotations.initMocks(this)
mvc = MockMvcBuilders.standaloneSetup(controller).setMessageConverters(MappingJackson2HttpMessageConverter()).build()
`when`(respository.save(User(1, "Test")))
.thenReturn(User(1, "Test"))
}
#Test
fun createUser() {
//val created = MockMvcResultMatchers.status().isCreated
var user = User(2, "Test")
var jsonData = jacksonObjectMapper().writeValueAsString(user)
mvc.perform(MockMvcRequestBuilders.post("/users/")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonData))
.andExpect(MockMvcResultMatchers.status().isOk)
//.andExpect(created)
.andDo(MockMvcResultHandlers.print())
.andReturn()
}
#Test
fun findUser() {
val ok = MockMvcResultMatchers.status().isOk
val builder = MockMvcRequestBuilders.get("/users?id=99") //no matther which id I type here it returns ok. I would expect only return for 1 based on my #BeforeEach
this.mvc.perform(builder)
.andExpect(ok)
}
}
controller
package com.mycomp.jokenpo.controller
import com.mycomp.jokenpo.model.User
import com.mycomp.jokenpo.respository.UserRepository
import com.mycomp.jokenpo.service.UserService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.util.concurrent.atomic.AtomicLong
import javax.validation.Valid
#RestController
#RequestMapping("users")
class UserController (private val userService: UserService, private val userRepository: UserRepository){
val counter = AtomicLong()
// #GetMapping("/user")
// fun getUser(#RequestParam(value = "name", defaultValue = "World") name: String) =
// User(counter.incrementAndGet(), "Hello, $name")
#GetMapping()
fun getAllUsers(): List<User> =
userService.all()
#PostMapping
fun add(#Valid #RequestBody user: User): ResponseEntity<User> {
//user.id?.let { userService.save(it) }
val savedUser = userService.save(user)
return ResponseEntity.ok(savedUser)
}
#GetMapping("/{id}")
fun getUserById(#PathVariable(value = "id") userId: Long): ResponseEntity<User> {
return userRepository.findById(userId).map { user ->
ResponseEntity.ok(user)
}.orElse(ResponseEntity.notFound().build())
}
#DeleteMapping("/{id}")
fun deleteUserById(#PathVariable(value = "id") userId: Long): ResponseEntity<Void> {
return userRepository.findById(userId).map { user ->
userRepository.deleteById(user.id)
ResponseEntity<Void>(HttpStatus.OK)
}.orElse(ResponseEntity.notFound().build())
}
// #DeleteMapping("{id}")
// fun deleteUserById(#PathVariable id: Long): ResponseEntity<Unit> {
// if (noteService.existsById(id)) {
// noteService.deleteById(id)
// return ResponseEntity.ok().build()
// }
// return ResponseEntity.notFound().build()
// }
/////
// #PutMapping("{id}")
// fun alter(#PathVariable id: Long, #RequestBody user: User): ResponseEntity<User> {
// return userRepository.findById(userId).map { user ->
// userRepository. deleteById(user.id)
// ResponseEntity<Void>(HttpStatus.OK)
// }.orElse(ResponseEntity.notFound().build())
// }
}
Repository
package com.mycomp.jokenpo.respository
import com.mycomp.jokenpo.model.User
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, Long>
Model
package com.mycomp.jokenpo.model
import javax.persistence.*
#Entity
data class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
#Column(nullable = false)
val name: String
)
gradle dependencies
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.2.6.RELEASE"
id("io.spring.dependency-management") version "1.0.9.RELEASE"
kotlin("jvm") version "1.3.71"
kotlin("plugin.spring") version "1.3.71"
kotlin("plugin.jpa") version "1.3.71"
}
group = "com.mycomp"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
val developmentOnly by configurations.creating
configurations {
runtimeClasspath {
extendsFrom(developmentOnly)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
//runtimeOnly("org.hsqldb:hsqldb")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation ("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
application.yml
spring:
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
driver-class-name: org.h2.Driver
platform: h2
h2:
console:
enabled: true
path: /h2-console #jdbc:h2:mem:testdb
In case it is usefull the whole project can be dowloaded from https://github.com/jimisdrpc/games but I am confident that all files above are enough to ilustrate my issue.
To solve your problem I suggest using #MockBean, an annotation that can be used to add mocks to a Spring ApplicationContext.
I would re-write your test as follows (notice that I'm taking advantage of mockito-kotlin already being a test dependency of your project):
package com.mycomp.jokenpo
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.mycomp.jokenpo.model.User
import com.mycomp.jokenpo.respository.UserRepository
import com.nhaarman.mockitokotlin2.whenever
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.junit.jupiter.MockitoExtension
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post
import org.springframework.web.util.NestedServletException
#AutoConfigureMockMvc. // auto-magically configures and enables an instance of MockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// Why configure Mockito manually when a JUnit 5 test extension already exists for that very purpose?
#ExtendWith(SpringExtension::class, MockitoExtension::class)
class JokenpoApplicationTests {
#Autowired
private lateinit var mockMvc: MockMvc
#MockBean
lateinit var respository: UserRepository
#BeforeEach
fun setup() {
// use mockito-kotlin for a more idiomatic way of setting up your test expectations
whenever(respository.save(User(1, "Test"))).thenAnswer {
it.arguments.first()
}
}
#Test
fun `Test createUser in the happy path scenario`() {
val user = User(1, "Test")
mockMvc.post("/users/") {
contentType = MediaType.APPLICATION_JSON
content = jacksonObjectMapper().writeValueAsString(user)
accept = MediaType.APPLICATION_JSON
}.andExpect {
status { isOk }
content { contentType(MediaType.APPLICATION_JSON) }
content { json("""{"id":1,"name":"Test"}""") }
}
verify(respository, times(1)).save(user)
}
#Test
fun `Test negative scenario of createUser`() {
val user = User(2, "Test")
assertThrows<NestedServletException> {
mockMvc.post("/users/") {
contentType = MediaType.APPLICATION_JSON
content = jacksonObjectMapper().writeValueAsString(user)
accept = MediaType.APPLICATION_JSON
}
}
verify(respository, times(1)).save(user)
}
#Test
fun findUser() {
mockMvc.get("/users?id=99")
.andExpect {
status { isOk }
}
verify(respository, times(1)).findAll()
}
}
Having said that, here's some food for thought:
Any test needs to include verification to assert that the systems behaves as is expected under various types of scenarios including negative scenarios such as how do we check if the service failed to create a new User record in the DB.
I noticed you already have a Test DB setup in your ApplicationContext (H2) so why not use it to create test records instead of just mocking the repository layer? Then you can verify the DB contains any newly created records.
As a general rule, I avoid using Mockito with Kotlin tests (search StackOverflow for a couple of reasons why), or even mockito-kotlin. Best practice nowadays is to use the excellent MockK library in combination with either AssertJ or assertk for verifying your expectations.
To run get unit tests running in following setup:
- Kotlin
- Spring Boot
- JUnit 5
- Mockito
- Gradle
you need this configuration, to get started:
build.gradle.kts
dependencies {
// ...
testRuntimeOnly(group = "org.junit.jupiter", name = "junit-jupiter-engine", version = "5.6.3")
testImplementation(group = "org.mockito", name = "mockito-all", version = "1.10.19")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vin tage", module = "junit-vintage-engine")
}
// ...
}
tasks.withType<Test> {
useJUnitPlatform()
}
test file
#org.springframework.boot.test.context.SpringBootTest
class YourTest {
#org.mockito.Mock
lateinit var testingRepo: TestingRepo
#org.mockito.InjectMocks
lateinit var testingService: TestingService
#org.springframework.test.context.event.annotation.BeforeTestMethod
fun initMocks() {
org.mockito.MockitoAnnotations.initMocks(this)
}
#org.junit.jupiter.api.Test
fun yourTest() {org.junit.jupiter.api.Assertions.assertTrue(true)}
}

Spring boot rest : Circular view path [error]: would dispatch back to the current handler URL [/error] again

My issue is I get 404 error when calling the spring boot application on localhost:8080/users
package com.myproj.users.controller;
import java.nio.file.attribute.UserPrincipalNotFoundException;
import java.security.Principal;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.myproj.users.Greeting;
import com.myproj.users.PhysicalCharacteristicsRepository;
import com.myproj.users.UserRepository;
import com.myproj.users.UserResource;
#RestController
#RequestMapping("/users")
public class UserRestController {
private UserRepository userRepository;
private PhysicalCharacteristicsRepository characteristicsRepository;
#RequestMapping(value = "/greeting/", method = RequestMethod.GET)
public String greeting() throws UserPrincipalNotFoundException {
return "Greeting";
}
#RequestMapping(value = "/error/")
public String error() {
return "Error handling";
}
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody Greeting sayHello(#RequestParam(value = "name", required = false, defaultValue = "Stranger") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
#Autowired
UserRestController(UserRepository userRepository, PhysicalCharacteristicsRepository characteristicsRepository) {
this.userRepository = userRepository;
this.characteristicsRepository = characteristicsRepository;
}
}
package com.myproj.users.controller;
import java.nio.file.attribute.UserPrincipalNotFoundException;
import org.springframework.hateoas.VndErrors;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.weather.exceptions.UserNotFoundException;
#ControllerAdvice
class UserControllerAdvice {
#ResponseBody
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
VndErrors userNotFoundExceptionHandler(UserNotFoundException ex) {
return new VndErrors("error", ex.getMessage());
}
#ResponseBody
#ExceptionHandler(UserPrincipalNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
VndErrors userPrincipalNotFoundException(UserPrincipalNotFoundException ex) {
return new VndErrors("error", ex.getMessage());
}
}
package com.myproj.users;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
I have tested the spring project in https://spring.io/guides/gs/actuator-service/ and it worked so I ignore what's going on.
I have defined a controller to manage errors. I have copied it from Spring Boot Remove Whitelabel Error Page
The new Application class is the following :
package com.test;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#Configuration
#ComponentScan(basePackages = "com.test")
#EnableAutoConfiguration
#EnableJpaRepositories(basePackages = "com.test")
#EntityScan(basePackages = "com.test")
public class Application {
static final Logger logger = LogManager.getLogger(Application.class.getName());
public static void main(String[] args) {
logger.debug("Entered the application");
SpringApplication.run(Application.class, args);
}
private Application() {
}
}
As you can see I have added a controller in ComponentScan as follows :
#ComponentScan(basePackages = "com.test")
#EnableJpaRepositories(basePackages = "com.test")
#EntityScan(basePackages = "com.test")
To test I used curl curl http://localhost:9002/eleves/Hammami/ and firefox.
Changing #Controller to #RestController solved my issue.
In my case I was using thymeleaf(MVC), after that I switched to pure backend.

Resources