Unit test POST API in spring-boot + kotlin + Junit - spring-boot

I'm pretty new to spring boot and kotlin. I've started with one basic app from net and writing unit test, but I'm getting following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: articleRepository.save(article) must not be null
Let me show you the code: Entity Class
#Entity
data class Article (
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
#get: NotBlank
val title: String = "",
#get: NotBlank
val content: String = ""
)
controller:
#PostMapping("/articles")
fun createNewArticle(#Valid #RequestBody article: Article) : Article {
return articleRepository.save(article)
}
Repository:
#Repository
interface ArticleRepository : JpaRepository<Article, Long>
Test File:
RunWith(SpringRunner::class)
#SpringBootTest
class KotlinDemoApplicationTests {
lateinit var mvc: MockMvc
#InjectMocks
lateinit var controller: ArticleController
#Mock
lateinit var respository: ArticleRepository
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
mvc = MockMvcBuilders.standaloneSetup(controller).setMessageConverters(MappingJackson2HttpMessageConverter()).build()
}
#Test
fun createBlog() {
var article = Article(1, "Test", "Test Content")
var jsonData = jacksonObjectMapper().writeValueAsString(article)
mvc.perform(MockMvcRequestBuilders.post("/api/articles/").contentType(MediaType.APPLICATION_JSON).content(jsonData))
.andExpect(MockMvcResultMatchers.status().isOk)
.andDo(MockMvcResultHandlers.print())
.andReturn()
}
}
When I'm running this test file, getting error mentioned above.
Please help me with this.

The problem is your ArticleRepository mock.
While you correctly inject it into your Controller, you're not specifiying what a call to save should return. It therefore returns null, which is not allowed in Kotin because you specified it as non-optional.
Either you allow your controller's createNewArticle to return null, by adding a ?, that is changing its signature to
fun createNewArticle(#Valid #RequestBody article: Article) : Article? {...}
Or you set-up the mock so that it does not return null, but an article.
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
...
`when`(respository.save(any())
.thenReturn(Article()) // creates a new article
}
(Alternatively, there's also Mockito's returnsFirstArg() in case you don't want to invoke the construtor.)
Note that using any() in this case will only work if you're using mockito-kotlin
If you don't want to use it, check this answer

Related

How to mock ModelMapper for list in SpringBootTest using Junit and Mockito?

I have one API in which I am returning a list of DTO by mapping it with main entity object using model mapper
employeeList = employeeRepository.findAll();
employeeListPojos = employeeList.stream().map((emp) -> modelMapper.map(emp, EmployeeInfoPojo.class))
.collect(Collectors.toList());
I am trying to mock the model mapper in my test class but the same output is overriding in the test case
#ExtendWith(MockitoExtension.class)
public class EmployeeServiceTest {
#Mock
private EmployeeRepository employeeRepository;
#Mock
private ModelMapper modelMapper;
#InjectMocks
private EmployeesApiServiceImpl employeesApiService;
EmployeeEntity employee;
#BeforeEach
public void setup()
{
employee = EmployeeEntity.builder()
.id("1L")
.employeeName("Test employee")
.description("Dummy employee")
.build();
}
#DisplayName("Test Case For Getting The employee Object")
#Test
public void givenemployeeObject_whenGetemployeeHeader_thenReturnemployeeObject()
{
//given - precondition or setup
EmployeeEntity employee2 = employeeEntity.builder()
.id("2L")
.employeeName("Test employee 1")
.description("Dummy employee")
.build();
List<EmployeeEntity> employees = new ArrayList<EmployeeEntity>();
employees.add(employee);
employees.add(employee2);
BDDMockito.given(employeeRepository.findAll()).willReturn(employees);
employeeInfoPojo convertedPojo1 = employeeInfoPojo.builder()
.id("1L")
.employeeName("Test employee 2")
.description("Dummy employee")
.build();
EmployeeInfoPojo convertedPojo2 = employeeInfoPojo.builder()
.id("2L")
.employeeName("Test employee")
.description("Dummy employee")
.build();
List<EmployeeInfoPojo> employeesResult = new ArrayList<EmployeeInfoPojo>();
employeesResult.add(convertedPojo1);
employeesResult.add(convertedPojo2);
for(EmployeeInfoPojo co : employeesResult){
BDDMockito.when(modelMapper.map(any(),any()))
.thenReturn(co);
}
//when - action or behaviour need to be tested
List<EmployeeInfoPojo> result = employeesApiService.getemployeeList(null);
System.out.println(result);
//then - verify the output
Assertions.assertThat(result).isNotNull();
Assertions.assertThat(result.size()).isEqualTo(2);
}
}
Test case is passing but output of result is not correct the convertedPojo2 is overridden in both the entries of list.
Any suggestion how to mock ModelMapper that is used with list in Junit and Mockito.
Issue mostly in these lines of EmployeeServiceTest
for(EmployeeInfoPojo co : employeesResult){
BDDMockito.when(modelMapper.map(any(),any()))
.thenReturn(co);
}
The mock for modelMapper.map(...) will always return convertedPojo2 since you don't have any specific matcher in the for-loop. Each iteration of the for-loop will override the last mock method and hence the last mock will be used.
Instead of setting up the mock in a for-loop, add the specific mock mapping, e.g something like this:
when(modelMapper.map(eq(employee), eq(EmployeeInfoPojo.class)))
.thenReturn(convertedPojo1);
when(modelMapper.map(eq(employee2), eq(EmployeeInfoPojo.class)))
.thenReturn(convertedPojo2);
This will set up a mock for the mapper when employee is used as parameter, convertedPojo1 will be returned for employee2, convertedPojo2 will be returned

Can't Autowire #Repository interface in Spring Boot

The problem appeared when I tried to migrate on WebFlux.
I have one package university. It contains 4 files: Document, Controller, Service and Repository.
#Document
data class University(
#Id
#JsonSerialize(using = ToStringSerializer::class)
var id: ObjectId?,
var name: String,
var city: String,
var yearOfFoundation: Int,
#JsonSerialize(using = ToStringSerializer::class)
var students: MutableList<ObjectId> = mutableListOf(),
#Version
var version: Int?
)
#Service
class UniversityService(#Autowired private var universityRepository: UniversityRepository) {
fun getAllUniversities(): Flux<University> =
universityRepository.findAll()
fun getUniversityById(id: ObjectId): Mono<University> =
universityRepository.findById(id)
}
#RestController
#RequestMapping("api/universities", consumes = [MediaType.APPLICATION_NDJSON_VALUE])
class UniversityController(#Autowired val universityService: UniversityService) {
#GetMapping("/all")
fun getAll(): Flux<University> =
universityService.getAllUniversities().log()
#GetMapping("/getById")
fun getUniversityById(#RequestParam("id") id: ObjectId): Mono<University> =
universityService.getUniversityById(id)
}
#Repository
interface UniversityRepository: ReactiveMongoRepository<University, ObjectId>, CustomUniversityRepository {
fun existsByNameIgnoreCase(name: String): Mono<Boolean>
fun removeUniversityById(id: ObjectId): Mono<University?>
fun findUniversitiesByNameIgnoreCase(name: String): Flux<University>
}
All in separate files regarding their names.
I am getting a problem with my service, cause it cannot find repository. Consider defining a bean of type 'demo.university.UniversityRepository' in your configuration. But my repository file with exact name and interface is directly there.
I've tried to mark my repository with Bean annotation, but I can't do so with interfaces. Also, #EnableJpaRepositories does not help.
P.S. I know, it seems like a duplicate, but I really didn't find an answer in previous questions.
My problem was in a wrong project dependencies. As I mentioned, I migrated from simple Web to WebFlux. But I didn't change my MongoDB dependency. It should be marked as a reactive explicitly even if ReactiveMongoRepository interface is found correctly.
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive:2.7.1")

Spring Validation: ConstraintViolationException for #Pattern due to password encoding

I'm just implementing a basic CRUD service where a user can be created in the database with their password matching a certain regex and being encoded using BCryptPasswordEncoder.
My tests are failing due to a ConstraintViolationException on the password saying that it does not satisfy the regex requirement:
javax.validation.ConstraintViolationException: Validation failed for classes [com.hoaxify.hoaxify.user.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$"', propertyPath=password, rootBeanClass=class com.hoaxify.hoaxify.user.User, messageTemplate='{javax.validation.constraints.Pattern.message}'}
It wasn't getting caught in my #ExceptionHandler since it was throwing a ConstraintViolationException and not a MethodArgumentNotValidException. I debugged and found that, while it was trying to match to the given regex, the value for the password itself was showing as:
$2a$10$pmRUViwj3Ey4alK0eqT1Dulz4BpGSlSReHyBR28K6bIE4.LZ7nYWG
while the password being passed in was:
P4ssword
So it appears the validation is being run on the encrypted password and not the raw password. I thought the validation should occur on the object received in the createUser method - before any other manipulation occurred.
Any help on why this is happening and how to fix would be greatly appreciated.
Note:
Validation works for all other fields
UserController
#RestController
#RequestMapping("{my/path}")
class UserController {
#Autowired
lateinit var userService: UserService
#PostMapping
fun createUser(#Valid #RequestBody user: User): GenericResponse {
userService.save(user)
return GenericResponse("Saved user")
}
#ExceptionHandler(MethodArgumentNotValidException::class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleValidationException(exception: MethodArgumentNotValidException, request: HttpServletRequest): ApiError {
val error = ApiError(400, "Validation error", request.servletPath)
val bindingResult = exception.bindingResult
bindingResult.fieldErrors.forEach {
error.validationErrors[it.field] = it.defaultMessage ?: "invalid"
}
return error
}
}
User
#Entity
class User(
#Id
#GeneratedValue
val id: Long,
#field:NotBlank
#field:Size(min = 4, max = 50)
var username: String,
#field:NotBlank
#field:Size(min = 4, max = 50)
var displayName: String,
#field:NotBlank
#field:Pattern(regexp = """^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$""")
var password: String
)
UserService
#Service
class UserService(
private val userRepository: UserRepository,
private val passwordEncoder: BCryptPasswordEncoder = BCryptPasswordEncoder()
) {
fun save(user: User): User {
user.password = passwordEncoder.encode(user.password)
return userRepository.save(user)
}
}
UserControllerTest
(relevant test)
#Test
fun postUser_whenUserIsValid_receiveOk() {
val user = User(
0,
"test-user",
"test-display",
"P4ssword"
)
val response: ResponseEntity<Any> = testRestTemplate.postForEntity(API_USERS_BASE, user, Any::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
The problem is that you use the same entity in the controller as in the service. So in the controller, it works as you expect. But in the service, you update the unencrypted password with the encrypted one and save that to the database. When you save to the database, the validation annotations are also checked, triggering the ConstraintViolationException.
The best option is to create a separate object for the controller. For example, create a CreateUserRequest class which is similar to the User entity, but only contains the fields that the controller needs. You can add your validation annotations there. Then in the service, convert the CreateUserRequest instance to a User entity. On the user class, remove the #Pattern validation since you don't want to validate the encrypted password.

Repository with simple cache in Kotlin

I'm trying to implement a simple caching of a logged in user in a repository which is linked to a room database.
Here is the code of the repository
#Singleton
class UserRepository #Inject constructor(
private val webservice: Webservice,
private val userDao: UserDao
) {
var user: User? = null //simple cache of a currently logged in user
private set
init {
user = null
}
fun getUser(userId: Int): LiveData<User> {
if (user != null)
return MutableLiveData<User>(user)
val data = userDao.load(userId)
//the value is always null because the load function is done async
user = data.value
return data
}
}
And here is the UserDao
#Dao
interface UserDao {
#Query("select * from user where id = :userId")
fun load(userId: Int): LiveData<User>
}
User data is feched by a ViewModel like so
class HomeViewModel #Inject constructor(
repository: UserRepository
) : ViewModel() {
val userId: Int = MainActivity.uid
val user: LiveData<User> = repository.getUser(userId)
}
The data is fetched from the database and displayed correctly but the loaded user is never asigned to the cache because the loading is done async. Is it possible to add some listener to the load function?
I'm quite new to kotlin and android in general so if there are some better solutions for this kind of simple cache please advise.

Customize endpoints with Spring Data REST

I've a project with Spring Boot 1.5.7, Spring Data REST, Hibernate, Spring JPA, Swagger2.
I've two beans like these:
#Entity
public class TicketBundle extends AbstractEntity {
private static final long serialVersionUID = 404514926837058071L;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Note> notes = new ArrayList<>();
.....
}
and
#Entity
public class Note extends AbstractEntity {
private static final long serialVersionUID = -5062313842902549565L;
#Lob
private String text;
...
}
I'm exposing my methods via Repository:
#Transactional
#RepositoryRestResource(excerptProjection = TicketBundleProjection.class)
#PreAuthorize("isAuthenticated()")
public interface TicketBundleRepository extends PagingAndSortingRepository<TicketBundle, Long> {
....
}
so in swagger I see the endpoint in which I'm interested that is needed to load the collection of notes from a specific ticket bundle:
Now, I want to override the default GET /api/v1/ticketBundles/{id}/notes and replace that with my custom method I put in TicketBundleRepository:
#Transactional(readOnly = true)
#RestResource(rel = "ticketBundleNotes", path = "/ticketBundles/{id}/notes")
#RequestMapping(method = RequestMethod.GET, path = "/ticketBundles/{id}/notes")
#Query("SELECT n FROM TicketBundle tb JOIN tb.notes n WHERE tb.id=:id ORDER BY n.createdDate DESC,n.id DESC")
public Page<Note> getNotes(#Param("id") long id, Pageable pageable);
It's very convenient create the query in this way because I need to use Pageable and return a Page. Unfortunately I've two problems at this point.
First problem
The method is mapped on the endpoint /api/v1/ticketBundles/search/ticketBundles/{id}/notes instad of /api/v1/ticketBundles/ticketBundles/{id}/notes
Second problem
When I call the method from swagger I receive an HTTP 404:
The request seems wrong. Seems the path variable is not understood:
curl -X GET --header 'Accept: application/json' 'http://localhost:8080/api/v1/ticketBundles/search/ticketBundles/{id}/notes?id=1'
This is the response from the server:
{
"timestamp": "2017-10-05T14:00:35.563+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/v1/ticketBundles/search/ticketBundles/%7Bid%7D/notes"
}
without any error on the server side.
Is there a way to override the endpoint GET/api/v1/ticketBundles/{id}/notes exposing it through Repository without using a custom controller (using that I would loose the facilities to manage the Pageable)?
Furthermore, what am I doing wrong to get a HTTP 404 in the call I shown above?
I believe you are using incorrect annotations. You would need to annotate your class with #RestController and use #PathVariable on your method instead of #Param. Here is a working sample, you may want to tailor it according to your needs.
#org.springframework.data.rest.webmvc.RepositoryRestController
#org.springframework.web.bind.annotation.RestController
public interface PersonRepository extends org.springframework.data.repository.PagingAndSortingRepository<Person, Long> {
#org.springframework.web.bind.annotation.GetMapping(path = "/people/{id}")
Person findById(#org.springframework.web.bind.annotation.PathVariable("id") Long id);
}

Resources