How to Autowire LdapRepository in Spring? - spring

I am attempting to use Spring's auto-configuration to spin up an embedded LDAP server and access it using spring-data-ldap. However, the autowired fields, repository (an instance of LdapRepository) and the ldapTemplate (an instance of the LdapTemplate) are null.
For example,
spring.ldap.model.UserTest > testSaveUser FAILED
java.lang.NullPointerException at UserTest.java:32
What am I missing?
build.gradle
plugins {
id 'org.springframework.boot' version '2.0.6.RELEASE' apply false
}
apply plugin: 'io.spring.dependency-management'
apply plugin: 'java'
repositories {
jcenter()
}
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
}
}
dependencies {
compile 'org.springframework.data:spring-data-ldap'
compile 'com.unboundid:unboundid-ldapsdk'
testCompile 'org.springframework.boot:spring-boot-starter-data-ldap'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
sourceCompatibility = '1.8'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
Under src/main/java/spring/ldap/model:
Config.java
package spring.ldap.model;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.ldap.repository.config.EnableLdapRepositories;
#Configuration
#EnableLdapRepositories
public class Config { }
User.java
package spring.ldap.model;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.DnAttribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
#Entry(objectClasses = { "person", "top" }, base="dc=spring,dc=ldap,dc=model" )
public class User {
#Id private Name dn;
#Attribute(name = "cn") #DnAttribute(value = "cn", index = 0) private String userName;
#Attribute(name = "sn") private String fullName;
public Name getDn() { return dn; }
public void setDn(Name dn) { this.dn = dn; }
public String getUsername() { return userName; }
public void setUsername(String userName) { this.userName = userName; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
}
UserRepository.java
package spring.ldap.model;
import org.springframework.data.ldap.repository.LdapRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends LdapRepository<User> { }
Under src/test/java/spring/ldap/model:
UserTest.java
package spring.ldap.model;
import static org.junit.Assert.assertNotNull;
import javax.naming.Name;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.test.context.ContextConfiguration;
#DataLdapTest()
#ContextConfiguration(classes = Config.class)
public class UserTest {
#Autowired UserRepository repository;
#Autowired LdapTemplate ldapTemplate;
#Before
public void SetUp() {
LdapNameBuilder builder = LdapNameBuilder.newInstance();
builder.add("dc", "model");
builder.add("dc", "ldap");
builder.add("dc", "spring");
Name dn = builder.build();
DirContextAdapter context = new DirContextAdapter(dn);
context.setAttributeValues("objectclass", new String[] { "top", "domain", "extensibleObject" });
context.setAttributeValue("dc", "spring");
ldapTemplate.bind(context);
}
#Test public void testSaveUser() {
User user = new User();
user.setUsername("ncornish");
user.setFullName("Nicholas Cornish");
repository.save(user);
assertNotNull("Id was not generated!", user.getDn());
}
}
Under src/test/resources:
application.properties
spring.ldap.embedded.base-dn=dc=spring,dc=ldap,dc=model
spring.ldap.embedded.credential.username=uid=admin
spring.ldap.embedded.credential.password=secret

Must put these annotations on test class context:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { UserRepository.class, LdapTemplate.class })
#ContextConfiguration(classes = Config.class)
#EnableConfigurationProperties
Kind regards.

Thanks JB Nezit, your comment
Your test doesn't run into a Spring context, since it's not annotated
with #RunWith(SpringRunner.class). So nothing will ever autowire
anything inside it.
resolves the issue. ie. add the following to the test class
#RunWith(SpringRunner.class)

Related

Junit tests are failing after I upgrade the spring boot dependencies

I have the following code that is running fine when I had spring boot dependencies 2.3.3.
class Details {
String pin;
State state;
}
class DetailsDto {
String pin;
StateDto stateDto;
}
class State {
}
class StateDto {
}
#Named
class TestClass {
private final StateConverter converter;
#Inject
public TestClass(StateConverter converter) {
this.converter= converter;
}
public DetailsDto getDetails(Details details) {
DetailsDto dto = new DetailsDto();
dto.setPin(details.getPin());
dto.setState(converter.convert(details.getState());
}
}
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
#RunWith(MockitoJUnitRunner.class)
public class TestClassTest {
#Mock
private StateConverter converter;
#InjectMocks
private TestClass testClass;
#Test
public final void testSomething() {
final String pin = "12345";
final State state = mock(State.class);
Details details = mock(Details.class);
given(details.getPin()).willReturn(pin);
given(details.getState()).willReturn(state);
StateDto stateDto = mock(StateDto.class);
given(converter.convert(state)).willReturn(stateDto);
DetailsDto deatilsDto = testClass.getDetails(details);
assertThat(deatilsDto.getPin(), equalTo(pin));
verify(converter).convert(state);
assertThat(deatilsDto.getState(), equalTo(stateDto));
}
}
The same test case I am running with spring boot dependencies 2.6.2 like this. The changes I have done are
I am using jupiter.api.test instead of junit.test, using ExtendWith instead of RunWith, using MockitoExtension instead of MockitoJUnitRunner
I am getting nullpointer exception when converter.convert is called
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
#ExtendWith(MockitoExtension.class)
public class TestClassTest {
#Mock
private StateConverter converter;
#InjectMocks
private TestClass testClass;
#Test
public final void testSomething() {
final String pin = "12345";
final State state = mock(State.class);
Details details = mock(Details.class);
given(details.getPin()).willReturn(pin);
given(details.getState()).willReturn(state);
StateDto stateDto = mock(StateDto.class);
given(converter.convert(state)).willReturn(stateDto);
DetailsDto deatilsDto = testClass.getDetails(details);
assertThat(deatilsDto.getPin(), equalTo(pin));
verify(converter).convert(state);
assertThat(deatilsDto.getState(), equalTo(stateDto));
}
}
This is due to using the wrong maven-surefire-plugin version
more at https://www.journaldev.com/21711/junit-setup-maven
junit5 tests need maven-surefire-plugin 2.22.0

Spring boot application failed to start with exception org.springframework.beans.factory.NoSuchBeanDefinitionException:

I am using spring boot v2.5.2. Below is my folder structure and code. This is simple test project.
My folder structure:
RESTController Class:
package com.user.UserManagementSystem.controller;
import com.user.UserManagementSystem.model.User;
import com.user.UserManagementSystem.service.UserServiceImpl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/v1/")
public class UserController {
#Autowired
private UserServiceImpl userRepository;
#GetMapping("/getAllUsers")
public List<User> getAllUsers() {
return userRepository.getUsers();
}
#GetMapping("/")
public String home() {
return "Hello";
}
}
User.java
package com.user.UserManagementSystem.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name= "Users")
public class User {
public User() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name ="userName")
private String userName;
#Column(name ="name")
private String name;
#Column(name ="language")
private String language;
#Column(name ="mobileNumber")
private int mobileNumber;
public User(String userName, String name, String language, int mobileNumber) {
this.userName = userName;
this.name = name;
this.language = language;
this.mobileNumber = mobileNumber;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public int getMobileNumber() {
return mobileNumber;
}
public void setMobileNumber(int mobileNumber) {
this.mobileNumber = mobileNumber;
}
}
UserRepository.java
package com.user.UserManagementSystem.repository;
import com.user.UserManagementSystem.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends JpaRepository<User, Long>{
}
UserService.java
package com.user.UserManagementSystem.service;
import com.user.UserManagementSystem.model.User;
import java.util.List;
public interface UserService {
List<User> getUsers();
User getUserById(Long id);
User addUser(User user);
void deleteUser(Long id);
}
UserServiceImpl.java
package com.user.UserManagementSystem.service;
import com.user.UserManagementSystem.repository.UserRepository;
import com.user.UserManagementSystem.model.User;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class UserServiceImpl implements UserService{
#Autowired
UserRepository userRepository;
#Override
public List<User> getUsers() {
return userRepository.findAll();
}
#Override
public User getUserById(Long id) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public User addUser(User user) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void deleteUser(Long id) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
UserManangmentSystemApplication.java
package com.user.UserManangmentSystem;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication(scanBasePackages = {"com.user.UserManangmentSystem", "com.user.UserManagementSystem.controller", "com.user.UserManagementSystem.repository", "com.user.UserManagementSystem.service"})
//#SpringBootApplication
public class UserManangmentSystemApplication {
public static void main(String[] args) {
SpringApplication.run(UserManangmentSystemApplication.class, args);
}
}
application.properties:
spring.datasource.url=jdbc:mariadb://localhost:3306/ums
spring.datasource.username=ums
spring.datasource.password=ums
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB53Dialect
server.port=8888
debug=true
When it build the project i am getting :
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.user.UserManagementSystem.repository.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1346) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657) ~[spring-beans-5.3.8.jar:5.3.8]
Thanks in Advance.
Typo in your base package name which makes packages different.
Change
com.user.UserManangmentSystem
To
com.user.UserManangementSystem
Correct management spelling.
You have correct package structure it will collect all bean within base package and sub package also. No need explicitly mention package scan. If you have any spell mistakes then that error will occur.

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)}
}

Drool Integration with spring-boot not working as expected

Problem: Rule(InterestRate.drl) getting fired in standalone java code and giving me accurate result
package com.test.drool.config;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import com.test.drool.facts.InterestRate;
public class RunSampleInterestRate {
public static void main(String[] args) {
// TODO Auto-generated method stub
KieContainer container= KieServices.Factory.get().getKieClasspathContainer();
InterestRate interestRate=new InterestRate();
interestRate.setLender("RBL");
System.out.println("printing session object before inserting"+interestRate.toString());
KieSession kieSession=container.newKieSession("ksession-rules");
kieSession.insert(interestRate);
kieSession.fireAllRules();
System.out.println(interestRate.getRate());
}
}
gives me expected 12.5 as interest rate.
Problem:I have to integrate this in rest service and I have been trying to test same logic under rest environment and it is not giving me expected results.After firing rules,service
always returns default value 0.0.My Environment is Spring-boot and drool is 6.5.0 final.
POJO:
package com.test.drool.facts;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown=true)
public class InterestRate {
#Override
public String toString() {
return "InterestRate [lender=" + lender + ", principal=" + principal + ", store=" + store
+ ", dealer=" + dealer + ", rate=" + rate + "]";
}
private String lender;
private String principal;
private String store;
private String dealer;
private double rate;
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public String getLender() {
return lender;
}
public void setLender(String lender) {
this.lender = lender;
}
public String getPrincipal() {
return principal;
}
public void setPrincipal(String principal) {
this.principal = principal;
}
public String getStore() {
return store;
}
public void setStore(String store) {
this.store = store;
}
public String getDealer() {
return dealer;
}
public void setDealer(String dealer) {
this.dealer = dealer;
}
}
Bean Config
package com.test.drool.config;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class DroolDependencyConfig {
#Bean(name="kieContainer")
public KieContainer kieContainer() {
return KieServices.Factory.get().getKieClasspathContainer();
}
}
Controller:
package com.test.drool.controllers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.test.drool.facts.InterestRate;
import com.test.drool.service.RuleExecuteService;
#RestController
#RequestMapping(value="/rule")
public class RuleExecuteController {
#Autowired
private RuleExecuteService executeService;
private static Logger logger=LoggerFactory.getLogger(RuleExecuteController.class);
#PostMapping(value = "/execute", consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Double> getInterestRate(#RequestBody InterestRate interestRate){
logger.info(String.format("logging Request Object %s",interestRate.toString()));
return new ResponseEntity<Double>(executeService.executeRule(interestRate),HttpStatus.OK);
}
}
RuleServiceImpl:
package com.test.drool.service.impl;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.drool.facts.InterestRate;
import com.test.drool.service.RuleExecuteService;
#Service
public class RuleExecutorServiceImpl implements RuleExecuteService {
private KieContainer kieContainer;
private static org.slf4j.Logger logger=LoggerFactory.getLogger(RuleExecutorServiceImpl.class);
#Autowired
public RuleExecutorServiceImpl(KieContainer kieContainer) {
this.kieContainer=kieContainer;
}
#Override
public double executeRule(InterestRate interestRate) {
logger.info("firing up session and executing rules");
KieSession kieSession= kieContainer.newKieSession("ksession-rules");
logger.info("Printing object before inserting in session"+interestRate.toString());
kieSession.insert(interestRate);
kieSession.fireAllRules();
System.out.println("returning values from rule execution"+">>>"+interestRate.getRate());
return interestRate.getRate();
}
}
DRL file:
package com.test.drool.facts
rule "Interest Rate"
when
$interestrate := InterestRate(lender.equals("RBL"))
then
$interestrate.setRate(12.30);
end
Gradle dependency:
dependencies {
compile "org.kie:kie-spring:${droolsVersion}"
compile "org.kie:kie-api:${droolsVersion}"
compile "org.drools:drools-core:${droolsVersion}"
compile "org.drools:drools-compiler:${droolsVersion}"
//compile('org.springframework.cloud:spring-cloud-starter-eureka')
compile('org.springframework.boot:spring-boot-starter-actuator')
compile ('org.springframework.boot:spring-boot-starter-web')
compile ('org.springframework.boot:spring-boot-starter-data-rest')
compile ('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.0'
}
I don't know the reason why it works but we kept minimum spring drool in our gradle file.All me and my colleague did was to have only kie-spring and drool-compiler in gradle and it worked like a charm.We also need to exclude some modules of spring since spring boot 1.5 does not work with spring 3.2 on which kie depends.For future spring boot drool development,here is gradle. Just paste it and build and code as you will code for any other spring-boot project.
repositories {
mavenCentral()
maven {
name 'jboss'
url 'http://repository.jboss.org/nexus/content/groups/public-jboss'
}
}
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
version = '0.0.1'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6'
}
}
dependencies {
compile ('org.kie:kie-spring:6.5.0.Final'){
exclude group:'org.springframework', module: 'spring-core'
exclude group:'org.springframework', module: 'spring-tx'
exclude group:'org.springframework', module: 'spring-beans'
exclude group:'org.springframework', module: 'spring-context'
}
compile "org.drools:drools-compiler:6.5.0.Final"
compile ('org.springframework.boot:spring-boot-starter-web')
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.0'
}
task createFolder{
def configDir = new File(project.buildDir.path+"/libs", "config")
def keystoreDir = new File(project.buildDir.path+"/libs", "keystore")
def logDir = new File(project.buildDir.path+"/libs", "log")
def libDir = new File(project.buildDir.path+"/libs", "lib")
if(!logDir.exists()){
logDir.mkdirs()
}
delete configDir
delete libDir
delete keystoreDir
libDir.mkdirs()
configDir.mkdirs()
keystoreDir.mkdirs()
}
//copy config
task copyConfig(type: Copy) {
into project.buildDir.path+"/libs/config"
from "config"
}
//copy keystore
task copyKeystore(type: Copy) {
into project.buildDir.path+"/libs/keystore"
from "keystore"
}
//copy dependencies
task copyRuntimeLibs(type: Copy) {
into project.buildDir.path+"/libs/lib"
from configurations.compile
}
task bundleAll(type: Jar){
dependsOn 'createFolder', 'copyRuntimeLibs', 'copyConfig', 'copyKeystore'
manifest {
def manifestClasspath = configurations.compile.collect { "lib/" + it.getName() }.join(' ')
attributes 'Implementation-Title': 'rule-service',
'Implementation-Version': version,
'Main-Class': 'com.test.drool.starte.RuleStarter',
'Class-Path': manifestClasspath
}
baseName=project.name
from { (configurations.compile - configurations.compile).collect { it.isDirectory() ? it : zipTree(it) } }
with jar
exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
}
task zip(type: Zip){
dependsOn 'bundleAll'
from 'build/libs'
}

Data is not getting inserted in Spring boot + Spring data JPA?

I have made a spring boot application, in the application I am spring-data-jpa. The problem is there while inserting the data in database, it shows the data is been inserted but there is no data in database. Following is my code. Please let me know where I am doing mistake. Thanks in advance.
Main Class
package avs.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootApplication
#EntityScan(basePackages = {"avs.pojo"})
#EnableJpaRepositories(basePackages = {"avs.repository"})
public class AVS {
public static void main(String[] args) {
SpringApplication.run(AVS.class, args);
}
}
Controller Class
package avs.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import avs.pojo.User;
import avs.repository.UserReposiroty;
#Controller
public class UserController {
#Autowired
private UserReposiroty userRepository;
#RequestMapping(value = "/saveuser", method = RequestMethod.POST)
#ResponseBody
#Bean
public String saveProduct(/*#RequestBody User user*/) {
User user = new User();
user.setUserId("ani");
user.setPassword("ani123");
user.setApplicatonId(123l);
userRepository.save(user);
return user.getUserId().toString();
}
}
Pojo Class
package avs.pojo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.springframework.stereotype.Component;
#Entity
#Component
#Table(name="user")
public class User extends BasePOJO {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="application_id")
private Long applicatonId;
#Column(name="user_id")
private String userId;
#Column(name="password")
private String password;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getApplicatonId() {
return applicatonId;
}
public void setApplicatonId(Long applicatonId) {
this.applicatonId = applicatonId;
}
#Override
public String toString(){
return String.format(
"Customer[id=%d, userId='%s', password='%s']",
id, userId, password);
}
}
Repository Class
package avs.repository;
import org.springframework.data.repository.CrudRepository;
import avs.pojo.User;
public interface UserReposiroty extends CrudRepository<User, Long> {
}
Application Properties
#port to run tomcat
server.port=8021
#database configuration
spring.datasource.url=jdbc:mysql://localhost/avs_db
spring.datasource.username=avsadmin
spring.datasource.password=avsadmin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# ===============================
# = JPA / HIBERNATE
# ===============================
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager).
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update): with "update" the database
# schema will be automatically updated accordingly to java entities found in
# the project
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
build gradle file
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'avs-bookcab'
version = '0.1.0'
}
repositories {
mavenCentral()
}
dependencies {
//Spring boot and MVC
compile group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: '1.5.2.RELEASE'
compile("org.springframework.boot:spring-boot-devtools")
//Spring boot and hibernate
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("com.h2database:h2")
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6'
}
If the H2 database is found on your classpath, Spring Boot will automatically set up an in memory H2 database for your use. Just remove H2 dependency from your gradle!
Change compile("com.h2database:h2") to test("com.h2database:h2") and it will do the trick (you most likely would like to have H2 for your future tests).
Also, some suggestions for you:
annotation #Bean above the public String saveProduct(/*#RequestBody User user*/) method is not needed;
annotation #Component above the public class User extends BasePOJO class is not needed;
if you move class public class AVS to the base package of the project
(package avs;) you can get rid of both annotations:
#EntityScan(basePackages = {"avs.pojo"})
#EnableJpaRepositories(basePackages = {"avs.repository"})
The problem with current implementation is, that if you add a Service class to avs.service package, it won't be picked up by spring and you will be forced to configure #ComponentScan, which is kind of an option, but #SpringBootApplication does it automatically, if it's in a right place.

Resources